@alecsibilia/luca 14.0.0-alpha.2 → 14.0.0-alpha.21

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 (321) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +7 -2
  3. package/board/client/board-look.ts +31 -1
  4. package/board/client/board-panel-parts.tsx +150 -0
  5. package/board/client/board-panel-stages.ts +99 -0
  6. package/board/client/board-panel-styles.ts +223 -0
  7. package/board/client/board-panel.tsx +77 -1275
  8. package/board/client/engine-life.ts +48 -0
  9. package/board/client/final-review-stack.tsx +144 -0
  10. package/board/client/heartbeat.tsx +61 -0
  11. package/board/client/helper-lines.tsx +69 -0
  12. package/board/client/live-feed.tsx +86 -0
  13. package/board/client/live-line.ts +73 -0
  14. package/board/client/model-line.tsx +178 -0
  15. package/board/client/needs-you-section.tsx +133 -0
  16. package/board/client/one-line.tsx +193 -0
  17. package/board/client/repo-wait-banner.tsx +69 -0
  18. package/board/client/run-footer.tsx +68 -0
  19. package/board/client/run-header.tsx +189 -0
  20. package/board/client/run-picker.tsx +98 -0
  21. package/board/client/status-bar.tsx +131 -0
  22. package/board/client/step-dots.tsx +127 -0
  23. package/board/client/stuck-actions.tsx +269 -0
  24. package/board/client/ticket-card-view.tsx +238 -0
  25. package/board/client/ticket-detail.tsx +87 -0
  26. package/board/client/ticket-stack.tsx +119 -0
  27. package/board/client/ticket-wait.ts +188 -0
  28. package/board/client/timeline-rows.tsx +41 -14
  29. package/board/client/usage-line.tsx +51 -0
  30. package/board/client/wait-line.tsx +39 -0
  31. package/board/index.client.tsx +1 -1
  32. package/board/index.server.ts +7 -1
  33. package/board/package.json +1 -1
  34. package/board/server/board-panel-state-helpers.ts +67 -0
  35. package/board/server/board-server-check.ts +294 -0
  36. package/board/server/board-server-engine.ts +129 -0
  37. package/board/server/board-server-events.ts +134 -0
  38. package/board/server/board-server-read.ts +190 -0
  39. package/board/server/board-server-resume.ts +164 -0
  40. package/board/server/board-server-start.ts +174 -0
  41. package/board/server/board-server-store.ts +281 -0
  42. package/board/server/board-server-types.ts +114 -0
  43. package/board/server/board-server-util.ts +4 -0
  44. package/board/server/board-server.ts +52 -943
  45. package/board/server/board-vocabulary-engine.ts +144 -0
  46. package/board/server/board-vocabulary-review.ts +115 -0
  47. package/board/server/board-vocabulary-run.ts +145 -0
  48. package/board/server/board-vocabulary-shapes.ts +153 -0
  49. package/board/server/board-vocabulary.ts +20 -375
  50. package/board/server/current-step-panel-helpers.ts +108 -0
  51. package/board/server/describe-record.ts +403 -0
  52. package/board/server/engine-launch.ts +20 -3
  53. package/board/server/engine-restart-helpers.ts +105 -0
  54. package/board/server/engine-watch.ts +45 -5
  55. package/board/server/journal-rebuild-helpers.ts +169 -0
  56. package/board/server/live-file.ts +130 -0
  57. package/board/server/luca-version.ts +1 -1
  58. package/board/server/make-rows.ts +14 -629
  59. package/board/server/record-texts.ts +350 -0
  60. package/board/server/reduce-board-agent-start.ts +224 -0
  61. package/board/server/reduce-board-agents.ts +160 -0
  62. package/board/server/reduce-board-counters.ts +138 -0
  63. package/board/server/reduce-board-create.ts +148 -0
  64. package/board/server/reduce-board-final-review.ts +174 -0
  65. package/board/server/reduce-board-needs-you.ts +283 -0
  66. package/board/server/reduce-board-reasons.ts +59 -0
  67. package/board/server/reduce-board-run.ts +255 -0
  68. package/board/server/reduce-board-settle.ts +88 -0
  69. package/board/server/reduce-board-steps.ts +226 -0
  70. package/board/server/reduce-board-text.ts +139 -0
  71. package/board/server/reduce-board-ticket-checks.ts +168 -0
  72. package/board/server/reduce-board-ticket-join.ts +144 -0
  73. package/board/server/reduce-board-ticket-start.ts +132 -0
  74. package/board/server/reduce-board-update.ts +117 -0
  75. package/board/server/reduce-board-usage.ts +247 -0
  76. package/board/server/reduce-board.ts +100 -1917
  77. package/board/server/reply-post.ts +407 -0
  78. package/board/server/run-registry.ts +8 -4
  79. package/board/server/ticket-stages-panel-helpers.ts +296 -0
  80. package/board/shared/board-rows.ts +9 -0
  81. package/board/shared/board-rpc.ts +42 -0
  82. package/board/shared/board-state-counts.ts +77 -0
  83. package/board/shared/board-state-live.ts +99 -0
  84. package/board/shared/board-state-review.ts +59 -0
  85. package/board/shared/board-state-run.ts +236 -0
  86. package/board/shared/board-state-ticket.ts +257 -0
  87. package/board/shared/board-state-usage.ts +107 -0
  88. package/board/shared/board-state.ts +97 -567
  89. package/board/shared/reply-actions.ts +207 -0
  90. package/engine/agents/agent-launcher.ts +27 -4
  91. package/engine/agents/claude-launcher-helpers.ts +233 -0
  92. package/engine/agents/claude-launcher.ts +28 -669
  93. package/engine/agents/claude-messages.ts +212 -0
  94. package/engine/agents/claude-options.ts +43 -8
  95. package/engine/agents/common-instructions.ts +138 -0
  96. package/engine/agents/input-channel.ts +36 -0
  97. package/engine/agents/launch-paths.ts +29 -0
  98. package/engine/agents/launch-session.ts +227 -0
  99. package/engine/agents/learner-instructions.ts +31 -0
  100. package/engine/agents/open-session.ts +210 -0
  101. package/engine/agents/prompt-sections.ts +95 -0
  102. package/engine/agents/rejoin-prompts.ts +135 -0
  103. package/engine/agents/reviewer-instructions.ts +99 -0
  104. package/engine/agents/role-instructions.ts +31 -244
  105. package/engine/agents/role-prompts.ts +39 -109
  106. package/engine/agents/role-results.ts +106 -7
  107. package/engine/agents/scripted-launcher.ts +21 -6
  108. package/engine/agents/session-pool.ts +124 -0
  109. package/engine/agents/turn-progress.ts +283 -0
  110. package/engine/agents/writer-instructions.ts +151 -0
  111. package/engine/cli/computer-adapters-real.ts +15 -3
  112. package/engine/cli/computer-adapters.ts +2 -0
  113. package/engine/cli/computer-checks.ts +87 -4
  114. package/engine/cli/config-reload-text.ts +39 -0
  115. package/engine/cli/decision-model-setup.ts +70 -0
  116. package/engine/cli/doctor-command.ts +1 -0
  117. package/engine/cli/doctor.ts +21 -3
  118. package/engine/cli/going-runs.ts +192 -36
  119. package/engine/cli/init-command.ts +1 -0
  120. package/engine/cli/init.ts +23 -1
  121. package/engine/cli/live-runs.ts +73 -0
  122. package/engine/cli/luca-run.ts +42 -7
  123. package/engine/cli/luca-skills.ts +183 -0
  124. package/engine/cli/luca.ts +8 -1
  125. package/engine/cli/models-command.ts +119 -0
  126. package/engine/cli/read-run.ts +81 -0
  127. package/engine/cli/run-args.ts +33 -6
  128. package/engine/cli/run-demo.ts +143 -0
  129. package/engine/cli/run-end.ts +184 -0
  130. package/engine/cli/run-modes-helpers.ts +81 -0
  131. package/engine/cli/run-modes.ts +62 -439
  132. package/engine/cli/setup.ts +5 -48
  133. package/engine/cli/unfinished-runs.ts +122 -0
  134. package/engine/cli/upgrade-command.ts +212 -30
  135. package/engine/cli/upgrade-finish.ts +149 -0
  136. package/engine/cli/upgrade.ts +112 -71
  137. package/engine/config/config-fields.ts +127 -0
  138. package/engine/config/config-reload.ts +250 -0
  139. package/engine/config/engine-config-schema.ts +235 -0
  140. package/engine/config/engine-config.ts +72 -142
  141. package/engine/config/test-commands.ts +102 -0
  142. package/engine/core/agent-guards-helpers.ts +84 -0
  143. package/engine/core/agent-turn.ts +395 -0
  144. package/engine/core/already-done.ts +44 -1
  145. package/engine/core/build-action.ts +214 -0
  146. package/engine/core/build-context.ts +135 -0
  147. package/engine/core/build-launch.ts +259 -0
  148. package/engine/core/commit-in.ts +282 -0
  149. package/engine/core/decide-build-early.ts +108 -0
  150. package/engine/core/decide-build-ending.ts +145 -0
  151. package/engine/core/decide-build-helpers.ts +87 -0
  152. package/engine/core/decide-build-tickets.ts +207 -0
  153. package/engine/core/decide-build-view.ts +103 -0
  154. package/engine/core/decide-build.ts +101 -1368
  155. package/engine/core/decide-final-fix.ts +203 -0
  156. package/engine/core/decide-final-review.ts +55 -310
  157. package/engine/core/decide-red-green.ts +398 -0
  158. package/engine/core/decide-rejoin.ts +259 -0
  159. package/engine/core/decide-repo-wait.ts +129 -0
  160. package/engine/core/decide-run-budget.ts +86 -12
  161. package/engine/core/decide-stuck-helpers.ts +127 -0
  162. package/engine/core/decide-stuck.ts +60 -8
  163. package/engine/core/decide-ticket-review.ts +95 -0
  164. package/engine/core/decide-ticket.ts +267 -0
  165. package/engine/core/decide-waits.ts +47 -0
  166. package/engine/core/decide.ts +74 -23
  167. package/engine/core/engine-chores.ts +163 -0
  168. package/engine/core/environment-rerun.ts +83 -0
  169. package/engine/core/evidence-text.ts +84 -0
  170. package/engine/core/execute-action.ts +303 -0
  171. package/engine/core/execute-already-done-check.ts +133 -0
  172. package/engine/core/execute-build.ts +68 -944
  173. package/engine/core/execute-changeset.ts +5 -7
  174. package/engine/core/execute-final-review.ts +125 -13
  175. package/engine/core/execute-gates.ts +182 -0
  176. package/engine/core/execute-intake.ts +130 -0
  177. package/engine/core/execute-join.ts +276 -0
  178. package/engine/core/execute-memory.ts +12 -0
  179. package/engine/core/execute-pull-request.ts +84 -0
  180. package/engine/core/execute-red-check.ts +219 -0
  181. package/engine/core/execute-repo-wait.ts +129 -0
  182. package/engine/core/execute-stuck.ts +25 -10
  183. package/engine/core/execute-waits.ts +158 -0
  184. package/engine/core/execute-with-jev.ts +118 -0
  185. package/engine/core/execute.ts +124 -831
  186. package/engine/core/final-failure-cap.ts +38 -0
  187. package/engine/core/final-review-action.ts +125 -0
  188. package/engine/core/final-review-text.ts +63 -17
  189. package/engine/core/final-stuck-text.ts +86 -0
  190. package/engine/core/fix-loop-text.ts +49 -2
  191. package/engine/core/left-out.ts +120 -0
  192. package/engine/core/live-file.ts +372 -0
  193. package/engine/core/loop-caps.ts +25 -2
  194. package/engine/core/not-in-run.ts +7 -3
  195. package/engine/core/pull-request-text.ts +255 -6
  196. package/engine/core/repo-wait-text.ts +81 -0
  197. package/engine/core/review-text.ts +55 -1
  198. package/engine/core/run-crash-recovery-helpers.ts +16 -0
  199. package/engine/core/run-session-close-helpers.ts +96 -0
  200. package/engine/core/run-start.ts +148 -0
  201. package/engine/core/run-stuck-text.ts +86 -0
  202. package/engine/core/schedule-rules.ts +193 -0
  203. package/engine/core/stuck-detail-text.ts +89 -0
  204. package/engine/core/stuck-format.ts +22 -0
  205. package/engine/core/stuck-text.ts +29 -332
  206. package/engine/core/ticket-stuck-text.ts +192 -0
  207. package/engine/core/worktree-changes.ts +106 -0
  208. package/engine/gates/already-done-check.ts +120 -0
  209. package/engine/gates/feature-map.ts +96 -0
  210. package/engine/gates/gate-runner.ts +108 -108
  211. package/engine/gates/gate-schemas.ts +61 -2
  212. package/engine/gates/leftover-scan.ts +154 -31
  213. package/engine/gates/new-tests.ts +45 -0
  214. package/engine/gates/own-files-check.ts +98 -0
  215. package/engine/gates/prepare-check.ts +60 -0
  216. package/engine/gates/prepare-slots.ts +99 -0
  217. package/engine/gates/red-check.ts +184 -16
  218. package/engine/gates/shell-check.ts +64 -0
  219. package/engine/gates/slot-waits.ts +55 -0
  220. package/engine/gates/slow-test-runner.ts +59 -0
  221. package/engine/gates/test-checks.ts +161 -0
  222. package/engine/gates/test-runner.ts +164 -5
  223. package/engine/gates/test-slots.ts +48 -0
  224. package/engine/git/git-adapter.ts +55 -396
  225. package/engine/git/git-commits.ts +132 -0
  226. package/engine/git/git-join.ts +139 -0
  227. package/engine/git/git-read.ts +123 -0
  228. package/engine/git/git-run.ts +32 -0
  229. package/engine/git/git-worktrees.ts +143 -0
  230. package/engine/git/outside-links.ts +55 -0
  231. package/engine/guards/after-turn-check.ts +31 -5
  232. package/engine/guards/guard-hook.ts +60 -12
  233. package/engine/guards/guard-roles.ts +68 -0
  234. package/engine/guards/helper-rules.ts +92 -0
  235. package/engine/guards/permission-rules.ts +161 -0
  236. package/engine/guards/role-rules-helpers.ts +48 -0
  237. package/engine/guards/role-rules.ts +34 -606
  238. package/engine/guards/shell-rules.ts +344 -0
  239. package/engine/guards/worktree-git.ts +208 -0
  240. package/engine/guards/worktree-snapshot.ts +412 -0
  241. package/engine/guards/worktree-state.ts +28 -825
  242. package/engine/guards/worktree-undo.ts +260 -0
  243. package/engine/guards/write-rules.ts +149 -0
  244. package/engine/index.ts +30 -9
  245. package/engine/intake/criteria-checks.ts +114 -0
  246. package/engine/intake/intake-checks.ts +164 -30
  247. package/engine/intake/intake-schemas.ts +37 -2
  248. package/engine/jev/clef-client.ts +259 -0
  249. package/engine/jev/decision-model-credentials.ts +212 -0
  250. package/engine/jev/jev-jobs.ts +144 -60
  251. package/engine/jev/jev-schemas.ts +86 -5
  252. package/engine/jev/jev-shadow-helpers.ts +218 -0
  253. package/engine/jev/jev-shadow.ts +109 -64
  254. package/engine/jev/model-pick.ts +275 -0
  255. package/engine/journal/agent-entries.ts +167 -0
  256. package/engine/journal/agent-message-entries.ts +72 -0
  257. package/engine/journal/agent-turn-replay.ts +203 -0
  258. package/engine/journal/already-done-entries.ts +55 -0
  259. package/engine/journal/commit-entries.ts +56 -0
  260. package/engine/journal/cross-ticket-replay.ts +222 -0
  261. package/engine/journal/decision-model-entries.ts +86 -0
  262. package/engine/journal/entry-fields.ts +10 -0
  263. package/engine/journal/final-review-entries.ts +128 -0
  264. package/engine/journal/final-review-replay.ts +324 -0
  265. package/engine/journal/final-review-round.ts +320 -0
  266. package/engine/journal/final-review-state.ts +180 -0
  267. package/engine/journal/gate-entries.ts +81 -0
  268. package/engine/journal/intake-entries.ts +87 -0
  269. package/engine/journal/join-entries.ts +126 -0
  270. package/engine/journal/journal-entry-schema.ts +218 -0
  271. package/engine/journal/journal-kinds.ts +97 -0
  272. package/engine/journal/journal-record-schema.ts +218 -0
  273. package/engine/journal/journal-record.ts +43 -1400
  274. package/engine/journal/journal.ts +6 -1
  275. package/engine/journal/limit-entries.ts +90 -0
  276. package/engine/journal/memory-entries.ts +124 -0
  277. package/engine/journal/memory-replay.ts +157 -0
  278. package/engine/journal/plan-replay.ts +139 -0
  279. package/engine/journal/pull-request-entries.ts +41 -0
  280. package/engine/journal/replay.ts +157 -2144
  281. package/engine/journal/reply-replay.ts +144 -0
  282. package/engine/journal/repo-wait-entries.ts +53 -0
  283. package/engine/journal/run-entries.ts +172 -0
  284. package/engine/journal/run-state.ts +264 -0
  285. package/engine/journal/shared-failure-replay.ts +133 -0
  286. package/engine/journal/stuck-entries.ts +193 -0
  287. package/engine/journal/ticket-progress.ts +392 -0
  288. package/engine/journal/ticket-replay.ts +411 -0
  289. package/engine/journal/ticket-result-replay.ts +364 -0
  290. package/engine/journal/ticket-retry-replay.ts +142 -0
  291. package/engine/journal/worktree-entries.ts +122 -0
  292. package/engine/models/model-score.ts +512 -0
  293. package/engine/shell/run-command.ts +187 -18
  294. package/engine/testing/agent-turn-fixtures.ts +287 -0
  295. package/engine/testing/already-done-fixtures.ts +97 -0
  296. package/engine/testing/build-fixtures.ts +68 -973
  297. package/engine/testing/checkouts-fixtures.ts +48 -0
  298. package/engine/testing/final-review-fixtures.ts +62 -1
  299. package/engine/testing/final-review-steps.ts +143 -0
  300. package/engine/testing/limit-fixtures.ts +85 -0
  301. package/engine/testing/live-file-fixtures.ts +57 -0
  302. package/engine/testing/practice-repo.ts +10 -4
  303. package/engine/testing/practice-run.ts +1 -1
  304. package/engine/testing/reply-fixtures.ts +88 -0
  305. package/engine/testing/run-budget-fixtures.ts +165 -0
  306. package/engine/testing/run-fixtures.ts +219 -0
  307. package/engine/testing/sdk-play.ts +177 -0
  308. package/engine/testing/setup-repo.ts +194 -0
  309. package/engine/testing/step-record-fixtures.ts +55 -0
  310. package/engine/testing/ticket-step-fixtures.ts +272 -0
  311. package/engine/testing/wait-scenarios.ts +186 -0
  312. package/engine/tracker/gh-retry.ts +217 -0
  313. package/engine/tracker/github-tracker.ts +310 -90
  314. package/engine/tracker/in-memory-tracker.ts +8 -4
  315. package/engine/tracker/tracker.ts +131 -6
  316. package/package.json +2 -1
  317. package/skills/luca-retro/SKILL.md +74 -0
  318. package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
  319. package/skills/luca-unstick/SKILL.md +153 -0
  320. package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
  321. package/engine/jev/jev-client.ts +0 -173
@@ -1,387 +1,25 @@
1
1
  import { z } from 'zod'
2
2
 
3
+ import { ENGINE_VOCABULARY } from './board-vocabulary-engine'
4
+ import { REVIEW_VOCABULARY } from './board-vocabulary-review'
5
+ import { RUN_VOCABULARY } from './board-vocabulary-run'
6
+
7
+ export type { LeftOutList } from './board-vocabulary-shapes'
8
+
3
9
  /**
4
10
  * The board's vocabulary: every journal kind the board reads, and the few
5
11
  * fields it reads from each. Schemas are loose on purpose, so the engine can
6
12
  * add fields without breaking the board, and the plugin never needs the
7
13
  * engine's code. A record whose content doesn't fit is skipped and logged.
14
+ *
15
+ * The kinds are in three modules (`board-vocabulary-run.ts`,
16
+ * `board-vocabulary-engine.ts`, `board-vocabulary-review.ts`) and the pieces
17
+ * they share in `board-vocabulary-shapes.ts`.
8
18
  */
9
-
10
- const FindingCountsSchema = z.looseObject({
11
- blocker: z.number().int().min(0).catch(0),
12
- should_fix: z.number().int().min(0).catch(0),
13
- nit: z.number().int().min(0).catch(0),
14
- })
15
-
16
- const FindingListSchema = z.array(
17
- z.looseObject({
18
- id: z.string().catch(''),
19
- severity: z.enum(['blocker', 'should_fix', 'nit']),
20
- })
21
- )
22
-
23
- /** A re-reviewer's ruling on a fixer's "won't fix" of a review finding. */
24
- const RulingListSchema = z
25
- .array(
26
- z.looseObject({
27
- finding_id: z.string(),
28
- ruling: z.enum(['accepted', 'rejected']),
29
- reason: z.string().catch(''),
30
- })
31
- )
32
- .catch([])
33
-
34
- /** A review fixer's answer to each finding: `fixed` or `wont_fix`. */
35
- const FindingResponseListSchema = z
36
- .array(
37
- z.looseObject({
38
- finding_id: z.string(),
39
- response: z.enum(['fixed', 'wont_fix']),
40
- reason: z.string().catch(''),
41
- })
42
- )
43
- .catch([])
44
-
45
- const TestRunSchema = z.looseObject({
46
- cases: z.array(z.looseObject({ status: z.string() })),
47
- })
48
-
49
- const WorktreeSchema = z.looseObject({ branch: z.string() })
50
-
51
- const tokenCount = z.number().min(0).catch(0)
52
-
53
- /** One plan window's reading: `utilization` 0 to 1, `resetsAt` in seconds. */
54
- const WindowReadingSchema = z.looseObject({
55
- utilization: z.number().optional().catch(undefined),
56
- resetsAt: z.number().optional().catch(undefined),
57
- })
58
-
59
- /**
60
- * One `rate_limit_event`'s info, as the Claude Agent SDK sends it:
61
- * `utilization` is 0 to 1, `resetsAt` is in seconds since the epoch. Real
62
- * readings carry no top-level `utilization`; each window's is in
63
- * `unifiedWindows`, keyed by window name (`five_hour`, `seven_day`, ...).
64
- */
65
- const RateLimitReadingSchema = z.looseObject({
66
- rateLimitType: z.string().optional().catch(undefined),
67
- utilization: z.number().optional().catch(undefined),
68
- resetsAt: z.number().optional().catch(undefined),
69
- unifiedWindows: z
70
- .record(z.string(), WindowReadingSchema.catch({}))
71
- .optional()
72
- .catch(undefined),
73
- })
74
-
75
- const TokensSchema = z
76
- .looseObject({
77
- input_tokens: tokenCount,
78
- output_tokens: tokenCount,
79
- cache_read_input_tokens: tokenCount,
80
- cache_creation_input_tokens: tokenCount,
81
- })
82
- .catch({
83
- input_tokens: 0,
84
- output_tokens: 0,
85
- cache_read_input_tokens: 0,
86
- cache_creation_input_tokens: 0,
87
- })
88
-
89
- /** The launcher's summary of an agent's session: tokens and readings. */
90
- const AgentSessionSchema = z.looseObject({
91
- /** The main loop's tokens. */
92
- usage: TokensSchema,
93
- /** The turn's tokens per model, subagents included; older journals have none. */
94
- model_usage: z.record(z.string(), TokensSchema).catch({}).default({}),
95
- rate_limit_events: z.array(RateLimitReadingSchema).catch([]),
96
- })
97
-
98
- const JevJobSchema = z.looseObject({ job: z.string() })
99
-
100
19
  export const BOARD_VOCABULARY = {
101
- run_started: z.looseObject({
102
- spec_number: z.number().int(),
103
- /** The engine config; the board reads only its run budget. */
104
- config: z
105
- .looseObject({
106
- run_budget_tokens: z
107
- .number()
108
- .int()
109
- .positive()
110
- .optional()
111
- .catch(undefined),
112
- })
113
- .optional()
114
- .catch(undefined),
115
- /** Luca's version the run started on (#460); older journals have none. */
116
- luca_version: z.string().nullable().catch(null),
117
- }),
118
- /** The engine started again on the run's journal, on Luca's `luca_version`. */
119
- engine_resumed: z.looseObject({ luca_version: z.string() }),
120
- intake_read: z.looseObject({
121
- spec: z.looseObject({ number: z.number(), title: z.string() }),
122
- }),
123
- intake_refused: z.looseObject({
124
- problems: z.array(
125
- z.looseObject({
126
- ticket: z.number().nullable(),
127
- missing: z.array(z.string()),
128
- })
129
- ),
130
- }),
131
- /** The run ends with nothing to do; `already_done` is empty at intake (#484). */
132
- nothing_to_do: z.looseObject({
133
- already_done: z.array(z.number().int()).catch([]).default([]),
134
- }),
135
- spec_snapshot: z.looseObject({
136
- spec: z.looseObject({ number: z.number(), title: z.string() }),
137
- ticket_order: z.array(z.number()).catch([]),
138
- }),
139
- ticket_snapshot: z.looseObject({
140
- number: z.number().int(),
141
- title: z.string(),
142
- labels: z.array(z.string()).catch([]),
143
- blockers: z.array(z.number().int()).catch([]),
144
- }),
145
- run_branch_created: WorktreeSchema,
146
- ticket_worktree_created: WorktreeSchema,
147
- baseline_tests: TestRunSchema,
148
- /** Another ticket's baseline, taken from the same run-branch commit (#404). */
149
- baseline_reused: z.looseObject({ from_ticket: z.number().int() }),
150
- agent_started: z.looseObject({
151
- role: z.string(),
152
- /** The session a fix-loop or failed-try follow-up went to. */
153
- follow_up_of: z.string().nullable().catch(null),
154
- }),
155
- agent_finished: z.looseObject({
156
- role: z.string(),
157
- result: z
158
- .looseObject({
159
- outcome: z.string().optional(),
160
- verdict: z.string().optional(),
161
- findings: FindingListSchema.optional(),
162
- /** The ticket-reviewer's rulings on declined findings. */
163
- rulings: RulingListSchema,
164
- /** A review fixer's answer to each finding it got. */
165
- finding_responses: FindingResponseListSchema,
166
- /** The learner's proposed memories (#370). */
167
- memories: z.array(z.unknown()).catch([]).optional(),
168
- /** The shown memories the learner said helped. */
169
- helped: z.array(z.string()).catch([]).optional(),
170
- })
171
- .catch({ rulings: [], finding_responses: [] }),
172
- }),
173
- agent_failed: z.looseObject({
174
- role: z.string(),
175
- error: z.string(),
176
- /** `agent`, `result`, `guard`, or `engine`. */
177
- failure: z.string().catch('agent'),
178
- }),
179
- agent_session: z.looseObject({
180
- role: z.string(),
181
- session: AgentSessionSchema,
182
- }),
183
- run_stopped: z.looseObject({
184
- reason: z.string(),
185
- role: z.string().nullable().catch(null),
186
- /** A billing stop: the session would bill per token. */
187
- billing: z.boolean().catch(false),
188
- /** A stop after the same step crashed again and again. */
189
- crashed: z.boolean().catch(false),
190
- }),
191
- worktree_reset: z.looseObject({}),
192
- /** The engine's install in a new worktree; `check` is null with no manifest. */
193
- dependencies_installed: z.looseObject({
194
- target: z.string(),
195
- check: z
196
- .looseObject({ command: z.string(), ok: z.boolean() })
197
- .nullable(),
198
- }),
199
- red_check: z.looseObject({
200
- ok: z.boolean(),
201
- problems: z.array(z.string()).catch([]),
202
- tests: TestRunSchema.optional(),
203
- }),
204
- leftover_scan: z.looseObject({
205
- /** `red`, `green`, or `fix` (a review fix round). */
206
- stage: z.string(),
207
- hits: z.array(z.looseObject({ path: z.string() })),
208
- }),
209
- /** `stage` is `red`, `green`, or `fix` (a review fix round). */
210
- commit_made: z.looseObject({ stage: z.string() }),
211
- gates_run: z.looseObject({
212
- target: z.string(),
213
- ok: z.boolean(),
214
- checks: z.array(z.looseObject({ name: z.string(), ok: z.boolean() })),
215
- }),
216
- ticket_joined: z.looseObject({
217
- ok: z.boolean(),
218
- error: z.string().optional(),
219
- }),
220
- /** A joined ticket sent back to be fixed on top of the run branch. */
221
- ticket_rebased: z.looseObject({
222
- /** `clash` or `join_gates`. */
223
- cause: z.string(),
224
- tests: z.array(z.string()).catch([]),
225
- code: z.array(z.string()).catch([]),
226
- }),
227
- run_branch_pushed: z.looseObject({ branch: z.string() }),
228
- ticket_stuck: z.looseObject({
229
- reason: z.string(),
230
- detail: z.string().catch(''),
231
- }),
232
- /** The whole run is stuck (#435): `run_budget`, it used up its budget. */
233
- run_stuck: z.looseObject({
234
- reason: z.string(),
235
- detail: z.string().catch(''),
236
- }),
237
- pull_request_opened: z.looseObject({
238
- number: z.number().int(),
239
- url: z.string(),
240
- }),
241
- /** The run's worktrees, removed at its end. Nothing visible changes. */
242
- worktrees_removed: z.looseObject({
243
- paths: z.array(z.string()).catch([]),
244
- }),
245
- // Jev in shadow mode: counted, never acted on.
246
- jev_asked: JevJobSchema,
247
- jev_answered: JevJobSchema,
248
- jev_failed: JevJobSchema,
249
- // Agent messages: sent inside an agent's turn, so they move nothing.
250
- agent_message: z.looseObject({
251
- from: z.string(),
252
- to: z.string(),
253
- text: z.string(),
254
- status: z.enum(['queued', 'not_delivered', 'refused']),
255
- reason: z.string().nullable().catch(null),
256
- }),
257
- agent_message_delivered: z.looseObject({
258
- ids: z.array(z.string()).catch([]),
259
- }),
260
- /** Another process changed the shared `.git` during an agent's turn. */
261
- shared_git_changed: z.looseObject({
262
- role: z.string().catch(''),
263
- changes: z.array(z.string()).catch([]),
264
- }),
265
- limit_wait_started: z.looseObject({
266
- /** When the limit resets; `null` when not known. */
267
- resets_at: z.string().nullable().catch(null),
268
- /** When the engine wakes; always set by the engine. */
269
- until: z.string().nullable().catch(null),
270
- /** The window that was hit: `five_hour`, `seven_day`, ... */
271
- rate_limit_type: z.string().nullable().catch(null),
272
- }),
273
- limit_wait_ended: z.looseObject({}),
274
- /** The run paused at a usage line. */
275
- usage_line_wait_started: z.looseObject({
276
- /** `seven_day` or `five_hour`. */
277
- window: z.string(),
278
- /** The line, in percent. */
279
- line: z.number(),
280
- /** How full the window was, in percent. */
281
- percent: z.number(),
282
- /** When the window resets. */
283
- resets_at: z.string(),
284
- }),
285
- /** The run carries on: `reason` is `reset` or `line_raised`. */
286
- usage_line_wait_ended: z.looseObject({}),
287
- /** How much of the plan a ticket (scope `ticket`) or the run used. */
288
- usage_recorded: z.looseObject({
289
- scope: z.enum(['ticket', 'run']),
290
- ticket: z.number().int().nullable().catch(null),
291
- /** Percents 0 to 100 per window, before and after, and the difference. */
292
- windows: z
293
- .record(z.string(), z.looseObject({ used: z.number() }))
294
- .catch({}),
295
- }),
296
-
297
- /** The owner's reply on the spec issue, read by the engine (#366). */
298
- reply_received: z.looseObject({
299
- /** `ship` is only for a stuck final review. */
300
- word: z.enum(['retry', 'skip', 'stop', 'ship']),
301
- ticket: z.number().int().nullable().optional(),
302
- }),
303
- /** A reply the engine couldn't use; it answered why on the spec issue. */
304
- reply_ignored: z.looseObject({
305
- /** `no_ticket_named`, `not_stuck`, `nothing_stuck`, `ship_needs_final_review`, ... */
306
- reason: z.string(),
307
- }),
308
- /** How the engine took a `retry` for a stuck ticket. */
309
- ticket_retried: z.looseObject({
310
- /** `resume`, `restart` (the ticket was edited), or `refused`. */
311
- mode: z.enum(['resume', 'restart', 'refused']),
312
- /** Why the edited ticket isn't ready to build, for `refused`. */
313
- problems: z.array(z.string()).catch([]),
314
- }),
315
- /** `because`: `null` when the owner skipped it, else the skipped ticket it waits on. */
316
- ticket_skipped: z.looseObject({
317
- because: z.number().int().nullable().catch(null),
318
- }),
319
- /** A ticket whose work was already on the base branch (#484), done by `shas`. */
320
- ticket_already_done: z.looseObject({
321
- shas: z.array(z.string()).catch([]),
322
- }),
323
- /** The engine closed an already-done ticket, as the run opens no PR. */
324
- ticket_closed: z.looseObject({}),
325
-
326
- // The final review (#367). Its agent, gate, scan, commit, and push
327
- // records are the usual kinds with `ticket: null`.
328
- final_review_started: z.looseObject({}),
329
- lens_started: z.looseObject({ lens: z.string() }),
330
- lens_finished: z.looseObject({
331
- lens: z.string(),
332
- findings: FindingCountsSchema,
333
- }),
334
- final_review_fixing: z.looseObject({ round: z.number().int().min(0) }),
335
- final_review_stuck: z.looseObject({
336
- reason: z.string(),
337
- detail: z.string().catch(''),
338
- }),
339
- final_review_passed: z.looseObject({}),
340
- /** A `ship` reply to the stuck final review: the PR opens anyway. */
341
- final_review_shipped: z.looseObject({}),
342
-
343
- // Memory (#370): counted, and its learner shown. The learner's agent
344
- // records are the usual kinds with `ticket: null` and role `learner`.
345
- /** A search at a recall point: each vault's outcome, and what it showed. */
346
- memory_recalled: z.looseObject({
347
- /** `run_start`, `ticket`, `review`, or `fix_round`. */
348
- point: z.string(),
349
- vaults: z
350
- .array(
351
- z.looseObject({
352
- vault: z.string(),
353
- ok: z.boolean(),
354
- error: z.string().nullable().catch(null),
355
- })
356
- )
357
- .catch([]),
358
- memories: z.array(z.unknown()).catch([]),
359
- }),
360
- /** The learner's memories saved, refused, or failed. */
361
- memories_saved: z.looseObject({
362
- saves: z
363
- .array(
364
- z.looseObject({
365
- /** `added`, `updated`, `refused`, or `failed`. */
366
- outcome: z.string(),
367
- })
368
- )
369
- .catch([]),
370
- }),
371
- /** The engine gave up on the learner; the run ends without it. */
372
- learning_skipped: z.looseObject({ reason: z.string().catch('') }),
373
- /** With no PR, the new memories went on the spec issue. */
374
- memories_reported: z.looseObject({ count: z.number().int().catch(0) }),
375
-
376
- // The scheduler's steps (#403): what the engine is doing right now.
377
- /**
378
- * A step began. `key` is the scheduler's key (a ticket's number, `run`,
379
- * `final`, `lens:<lens>`, ...); `step` its action type, plus `:<role>`
380
- * for an agent's turn.
381
- */
382
- step_started: z.looseObject({ key: z.string(), step: z.string() }),
383
- /** The step on `key` settled. */
384
- step_ended: z.looseObject({ key: z.string(), step: z.string() }),
20
+ ...RUN_VOCABULARY,
21
+ ...ENGINE_VOCABULARY,
22
+ ...REVIEW_VOCABULARY,
385
23
  } as const
386
24
 
387
25
  export type BoardKind = keyof typeof BOARD_VOCABULARY
@@ -394,7 +32,11 @@ const entry = <Kind extends BoardKind>({ kind }: { kind: Kind }) =>
394
32
 
395
33
  const BoardEntrySchema = z.discriminatedUnion('kind', [
396
34
  entry({ kind: 'run_started' }),
35
+ entry({ kind: 'repo_wait_started' }),
36
+ entry({ kind: 'repo_wait_ended' }),
397
37
  entry({ kind: 'engine_resumed' }),
38
+ entry({ kind: 'config_reloaded' }),
39
+ entry({ kind: 'config_reload_refused' }),
398
40
  entry({ kind: 'intake_read' }),
399
41
  entry({ kind: 'intake_refused' }),
400
42
  entry({ kind: 'nothing_to_do' }),
@@ -417,6 +59,7 @@ const BoardEntrySchema = z.discriminatedUnion('kind', [
417
59
  entry({ kind: 'gates_run' }),
418
60
  entry({ kind: 'ticket_joined' }),
419
61
  entry({ kind: 'ticket_rebased' }),
62
+ entry({ kind: 'tests_sent_back' }),
420
63
  entry({ kind: 'run_branch_pushed' }),
421
64
  entry({ kind: 'ticket_stuck' }),
422
65
  entry({ kind: 'run_stuck' }),
@@ -425,6 +68,7 @@ const BoardEntrySchema = z.discriminatedUnion('kind', [
425
68
  entry({ kind: 'jev_asked' }),
426
69
  entry({ kind: 'jev_answered' }),
427
70
  entry({ kind: 'jev_failed' }),
71
+ entry({ kind: 'decision_model_off' }),
428
72
  entry({ kind: 'agent_message' }),
429
73
  entry({ kind: 'agent_message_delivered' }),
430
74
  entry({ kind: 'shared_git_changed' }),
@@ -452,6 +96,7 @@ const BoardEntrySchema = z.discriminatedUnion('kind', [
452
96
  entry({ kind: 'memories_reported' }),
453
97
  entry({ kind: 'step_started' }),
454
98
  entry({ kind: 'step_ended' }),
99
+ entry({ kind: 'ticket_waiting' }),
455
100
  ])
456
101
 
457
102
  /** A record the board understands, with its content parsed. */
@@ -0,0 +1,108 @@
1
+ import { type Harness } from './testing/board-harness'
2
+ import { intakeOfThree, stamp, type Entry } from './testing/journal-fixtures'
3
+
4
+ import type { EngineRecord } from '../shared/board-rpc'
5
+
6
+ /**
7
+ * Helpers shared by the current step tests (`current-step-panel.test.ts` and
8
+ * the `current-step-panel-*.test.ts` files beside it).
9
+ *
10
+ * The pure helpers that build and read journal records and the run header
11
+ * row. Each test file keeps its own `harness`, `extra`, `dirs`, and
12
+ * `runWith`, because the tests start boards of their own.
13
+ */
14
+
15
+ /**
16
+ * `step_started` as the engine journals it: `key` is the ticket's number,
17
+ * or `run` for the run's own steps; `step` is the action type, plus
18
+ * `:<role>` for an agent's turn.
19
+ */
20
+ export const stepStarted = ({
21
+ ticket,
22
+ step,
23
+ role = null,
24
+ first_seq = null,
25
+ }: {
26
+ ticket: number | null
27
+ step: string
28
+ role?: string | null
29
+ first_seq?: number | null
30
+ }): Entry => ({
31
+ kind: 'step_started',
32
+ ticket,
33
+ role,
34
+ content: {
35
+ key: ticket === null ? 'run' : String(ticket),
36
+ step,
37
+ first_seq,
38
+ },
39
+ })
40
+
41
+ /** `step_ended` as the engine journals it once the step settles. */
42
+ export const stepEnded = ({
43
+ ticket,
44
+ step,
45
+ role = null,
46
+ }: {
47
+ ticket: number | null
48
+ step: string
49
+ role?: string | null
50
+ }): Entry => ({
51
+ kind: 'step_ended',
52
+ ticket,
53
+ role,
54
+ content: { key: ticket === null ? 'run' : String(ticket), step },
55
+ })
56
+
57
+ /** The journal: three tickets through intake, then `entries`. */
58
+ export const journalWith = ({
59
+ entries,
60
+ }: {
61
+ entries: Entry[]
62
+ }): EngineRecord[] => stamp({ entries: [...intakeOfThree(), ...entries] })
63
+
64
+ /** The stamped time of the record at `seq`. */
65
+ export const timeOf = ({
66
+ records,
67
+ seq,
68
+ }: {
69
+ records: EngineRecord[]
70
+ seq: number
71
+ }): string => {
72
+ const found = records.find((record) => record.seq === seq)
73
+ if (!found) throw new Error(`no record ${seq}`)
74
+ return found.time
75
+ }
76
+
77
+ /** The seq of the last record of `kind` whose content has `step`. */
78
+ export const lastStepSeq = ({
79
+ records,
80
+ kind,
81
+ step,
82
+ ticket,
83
+ }: {
84
+ records: EngineRecord[]
85
+ kind: string
86
+ step: string
87
+ ticket: number | null
88
+ }): number => {
89
+ const found = records.findLast(
90
+ (record) =>
91
+ record.kind === kind &&
92
+ record.ticket === ticket &&
93
+ (record.content as { step: string }).step === step
94
+ )
95
+ if (!found) throw new Error(`no ${kind} for ${step}`)
96
+ return found.seq
97
+ }
98
+
99
+ /** The run's header row as the chat shows it now. */
100
+ export const headerOf = ({ board }: { board: Harness }) => {
101
+ const found = board
102
+ .latestRows()
103
+ .find(({ row }) => row.kind === 'luca-board-run')
104
+ if (!found || found.row.kind !== 'luca-board-run') {
105
+ throw new Error('no header row')
106
+ }
107
+ return found.row.data
108
+ }