@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
@@ -0,0 +1,296 @@
1
+ import { expect } from 'bun:test'
2
+
3
+ import { createHarness, type Harness } from './testing/board-harness'
4
+ import {
5
+ agentFinished,
6
+ agentStarted,
7
+ baselineTests,
8
+ commitMade,
9
+ finding,
10
+ gatesRun,
11
+ intakeOfThree,
12
+ leftoverScan,
13
+ redCheck,
14
+ reviewFinished,
15
+ runBranchPushed,
16
+ sessionOf,
17
+ ticketJoined,
18
+ ticketWorktreeCreated,
19
+ type Entry,
20
+ } from './testing/journal-fixtures'
21
+
22
+ /**
23
+ * Helpers shared by the ticket stage tests (`ticket-stages-panel.test.ts`
24
+ * and the `ticket-stages-panel-*.test.ts` files beside it).
25
+ *
26
+ * The harness is the one the running test started; `runWith` sets it, and
27
+ * each test file cleans it up after every test.
28
+ */
29
+
30
+ export let harness: Harness
31
+ export let sent = { run_id: '', token: '', next_seq: 1 }
32
+
33
+ /** Starts a run and sends intake plus `entries` in one go. */
34
+ export const runWith = async ({ entries }: { entries: Entry[] }) => {
35
+ harness = await createHarness()
36
+ const { run_id, token } = await harness.start()
37
+ const reply = await harness.send({
38
+ run_id,
39
+ token,
40
+ entries: [...intakeOfThree(), ...entries],
41
+ })
42
+ expect(reply.ok).toBe(true)
43
+ sent = { run_id, token, next_seq: reply.next_seq }
44
+ }
45
+
46
+ /** Sends more records to the same run, after the ones already sent. */
47
+ export const sendMore = async ({ entries }: { entries: Entry[] }) => {
48
+ const reply = await harness.send({
49
+ run_id: sent.run_id,
50
+ token: sent.token,
51
+ entries,
52
+ first_seq: sent.next_seq,
53
+ })
54
+ expect(reply.ok).toBe(true)
55
+ sent = { ...sent, next_seq: reply.next_seq }
56
+ }
57
+
58
+ /**
59
+ * `step_started` as the engine journals it: `key` is the ticket's number,
60
+ * or `run` for a step of no ticket; `step` is the action type, plus
61
+ * `:<role>` for an agent's turn.
62
+ */
63
+ export const stepStarted = ({
64
+ ticket,
65
+ step,
66
+ role = null,
67
+ }: {
68
+ ticket: number | null
69
+ step: string
70
+ role?: string | null
71
+ }): Entry => ({
72
+ kind: 'step_started',
73
+ ticket,
74
+ role,
75
+ content: {
76
+ key: ticket === null ? 'run' : String(ticket),
77
+ step,
78
+ first_seq: null,
79
+ },
80
+ })
81
+
82
+ /** `step_ended` as the engine journals it once the step settles. */
83
+ export const stepEnded = ({
84
+ ticket,
85
+ step,
86
+ role = null,
87
+ }: {
88
+ ticket: number | null
89
+ step: string
90
+ role?: string | null
91
+ }): Entry => ({
92
+ kind: 'step_ended',
93
+ ticket,
94
+ role,
95
+ content: { key: ticket === null ? 'run' : String(ticket), step },
96
+ })
97
+
98
+ /** A whole step: its start, the records it wrote, and its end. */
99
+ export const wholeStep = ({
100
+ ticket,
101
+ step,
102
+ role = null,
103
+ entries,
104
+ }: {
105
+ ticket: number | null
106
+ step: string
107
+ role?: string | null
108
+ entries: Entry[]
109
+ }): Entry[] => [
110
+ stepStarted({ ticket, step, role }),
111
+ ...entries,
112
+ stepEnded({ ticket, step, role }),
113
+ ]
114
+
115
+ /** An agent's turn starting: its step, then its `agent_started`. */
116
+ export const agentTurnStarts = ({
117
+ ticket,
118
+ role,
119
+ follow_up = false,
120
+ }: {
121
+ ticket: number
122
+ role: string
123
+ follow_up?: boolean
124
+ }): Entry[] => [
125
+ stepStarted({
126
+ ticket,
127
+ step: `${follow_up ? 'follow_up_agent' : 'launch_agent'}:${role}`,
128
+ role,
129
+ }),
130
+ agentStarted({
131
+ ticket,
132
+ role,
133
+ follow_up_of: follow_up ? sessionOf({ ticket, role }) : null,
134
+ }),
135
+ ]
136
+
137
+ /** An agent's turn ending: its result, then its step's end. */
138
+ export const agentTurnEnds = ({
139
+ ticket,
140
+ role,
141
+ follow_up = false,
142
+ finished,
143
+ }: {
144
+ ticket: number
145
+ role: string
146
+ follow_up?: boolean
147
+ finished?: Entry
148
+ }): Entry[] => [
149
+ finished ?? agentFinished({ ticket, role }),
150
+ stepEnded({
151
+ ticket,
152
+ step: `${follow_up ? 'follow_up_agent' : 'launch_agent'}:${role}`,
153
+ role,
154
+ }),
155
+ ]
156
+
157
+ /** A commit of `stage`, with its leftover scan, as one step. */
158
+ export const committed = ({
159
+ ticket,
160
+ stage,
161
+ }: {
162
+ ticket: number
163
+ stage: 'red' | 'green' | 'fix'
164
+ }): Entry[] =>
165
+ wholeStep({
166
+ ticket,
167
+ step: 'commit_ticket',
168
+ entries: [
169
+ leftoverScan({ ticket, stage }),
170
+ commitMade({ ticket, stage }),
171
+ ],
172
+ })
173
+
174
+ /** The checks running on the ticket's worktree (or the run branch). */
175
+ export const gatesStart = ({ ticket }: { ticket: number }): Entry =>
176
+ stepStarted({ ticket, step: 'run_gates' })
177
+
178
+ export const gatesEnd = ({
179
+ ticket,
180
+ ok,
181
+ target = 'ticket',
182
+ }: {
183
+ ticket: number
184
+ ok: boolean
185
+ target?: 'ticket' | 'run_branch'
186
+ }): Entry[] => [
187
+ gatesRun({ ticket, ok, target }),
188
+ stepEnded({ ticket, step: 'run_gates' }),
189
+ ]
190
+
191
+ export const blockerOnly = [finding({ id: 'R1-1', severity: 'blocker' })]
192
+
193
+ /** One point in a ticket's life: the records up to it, and its stage then. */
194
+ export type Checkpoint = { at: string; entries: Entry[]; stage: string }
195
+
196
+ /**
197
+ * Sends each checkpoint's records in order and reads the ticket's stage
198
+ * after each, as `<at>: <stage>` lines, so a wrong stage names its point.
199
+ */
200
+ export const stagesAlong = async ({
201
+ ticket,
202
+ checkpoints,
203
+ }: {
204
+ ticket: number
205
+ checkpoints: Checkpoint[]
206
+ }): Promise<string[]> => {
207
+ const seen: string[] = []
208
+ for (const { at, entries } of checkpoints) {
209
+ await sendMore({ entries })
210
+ seen.push(`${at}: ${(await harness.ticket({ number: ticket })).stage}`)
211
+ }
212
+ return seen
213
+ }
214
+
215
+ export const expectedAlong = ({
216
+ checkpoints,
217
+ }: {
218
+ checkpoints: Checkpoint[]
219
+ }): string[] => checkpoints.map(({ at, stage }) => `${at}: ${stage}`)
220
+
221
+ export const stageOf = async ({ ticket }: { ticket: number }) =>
222
+ (await harness.ticket({ number: ticket })).stage
223
+
224
+ /** Ticket `ticket` from its worktree to its tests committed. */
225
+ export const testsCommitted = ({ ticket }: { ticket: number }): Entry[] => [
226
+ ticketWorktreeCreated({ ticket }),
227
+ ...wholeStep({
228
+ ticket,
229
+ step: 'run_baseline_tests',
230
+ entries: [baselineTests({ ticket, passed: 4 })],
231
+ }),
232
+ ...agentTurnStarts({ ticket, role: 'test-writer' }),
233
+ ...agentTurnEnds({ ticket, role: 'test-writer' }),
234
+ ...wholeStep({
235
+ ticket,
236
+ step: 'run_red_check',
237
+ entries: [redCheck({ ticket, ok: true, failing: 2, passing: 4 })],
238
+ }),
239
+ ...committed({ ticket, stage: 'red' }),
240
+ ]
241
+
242
+ /** Ticket `ticket` up to its first ticket review asking for changes. */
243
+ export const reviewedOnce = ({ ticket }: { ticket: number }): Entry[] => [
244
+ ...testsCommitted({ ticket }),
245
+ ...agentTurnStarts({ ticket, role: 'implementer' }),
246
+ ...agentTurnEnds({ ticket, role: 'implementer' }),
247
+ gatesStart({ ticket }),
248
+ ...gatesEnd({ ticket, ok: true }),
249
+ ...committed({ ticket, stage: 'green' }),
250
+ ...agentTurnStarts({ ticket, role: 'ticket-reviewer' }),
251
+ ]
252
+
253
+ /** Ticket `ticket` approved by its ticket review, not yet joined. */
254
+ export const approved = ({ ticket }: { ticket: number }): Entry[] => [
255
+ ...reviewedOnce({ ticket }),
256
+ ...agentTurnEnds({
257
+ ticket,
258
+ role: 'ticket-reviewer',
259
+ finished: reviewFinished({ ticket }),
260
+ }),
261
+ ]
262
+
263
+ /** An approved ticket's join, its gates on the run branch, and the push. */
264
+ export const joinedAndPushed = ({ ticket }: { ticket: number }): Entry[] => [
265
+ ...wholeStep({
266
+ ticket,
267
+ step: 'join_run_branch',
268
+ entries: [ticketJoined({ ticket })],
269
+ }),
270
+ gatesStart({ ticket }),
271
+ ...gatesEnd({ ticket, ok: true, target: 'run_branch' }),
272
+ ...wholeStep({
273
+ ticket,
274
+ step: 'push_run_branch',
275
+ entries: [runBranchPushed({ ticket })],
276
+ }),
277
+ ]
278
+
279
+ /** The refactor ticket 11: no test-writer and no red check. */
280
+ export const refactorDone = (): Entry[] => [
281
+ ticketWorktreeCreated({ ticket: 11 }),
282
+ ...agentTurnStarts({ ticket: 11, role: 'implementer' }),
283
+ ...agentTurnEnds({ ticket: 11, role: 'implementer' }),
284
+ gatesStart({ ticket: 11 }),
285
+ ...gatesEnd({ ticket: 11, ok: true }),
286
+ ...committed({ ticket: 11, stage: 'green' }),
287
+ ...agentTurnStarts({ ticket: 11, role: 'ticket-reviewer' }),
288
+ ...agentTurnEnds({
289
+ ticket: 11,
290
+ role: 'ticket-reviewer',
291
+ finished: reviewFinished({ ticket: 11 }),
292
+ }),
293
+ ...joinedAndPushed({ ticket: 11 }),
294
+ ]
295
+
296
+ export const T = 13
@@ -85,6 +85,15 @@ export const StuckRowSchema = z.object({
85
85
  tried: z.array(z.string()),
86
86
  replies: z.array(z.string()),
87
87
  resolution: z.string().nullable(),
88
+ /**
89
+ * The run and the stuck item, for the row's reply buttons and "Help me"
90
+ * (#503). Defaulted, so a row added before them still renders (without
91
+ * the buttons).
92
+ */
93
+ run_id: z.string().nullable().default(null),
94
+ key: z.string().nullable().default(null),
95
+ ticket: z.number().int().nullable().default(null),
96
+ since: z.string().nullable().default(null),
88
97
  })
89
98
 
90
99
  export type StuckRow = z.infer<typeof StuckRowSchema>
@@ -6,6 +6,7 @@ import {
6
6
  EngineEndedSchema,
7
7
  RunSummarySchema,
8
8
  } from './board-state'
9
+ import { PostedReplySchema } from './reply-actions'
9
10
 
10
11
  /**
11
12
  * The plugin's RPCs. Payload keys are snake_case (API convention).
@@ -14,6 +15,7 @@ import {
14
15
  * - `engine.event`: the engine sends its journal records to the board.
15
16
  * - `board.read`: the side panel polls the runs of one workspace.
16
17
  * - `board.version`: `luca doctor` asks which Luca version is loaded.
18
+ * - `reply.post`: a reply button on stuck work posts its word on the spec.
17
19
  */
18
20
 
19
21
  /**
@@ -105,6 +107,13 @@ export const BoardReadInputSchema = z.object({
105
107
  export const BoardReadOutputSchema = z.object({
106
108
  runs: z.array(RunSummarySchema),
107
109
  selected: BoardStateSchema.nullable(),
110
+ /**
111
+ * The chat the selected run started in, for "Help me"; `null` for a run
112
+ * the plugin didn't start. Defaulted, so an older daemon's answer parses.
113
+ */
114
+ chat_agent_id: z.string().nullable().default(null),
115
+ /** The replies the board posted for the selected run (#503). */
116
+ posted: z.array(PostedReplySchema).default([]),
108
117
  })
109
118
 
110
119
  export const boardReadRpc = defineRpc({
@@ -129,3 +138,36 @@ export const boardVersionRpc = defineRpc({
129
138
  input: z.object({}),
130
139
  output: BoardVersionOutputSchema,
131
140
  })
141
+
142
+ /**
143
+ * API Request: post one reply on a run's spec issue (#503). `key` is the
144
+ * stuck item's (`ticket-<n>`, `final`, or `run`), and `reply` one of the
145
+ * words the engine takes for it, such as `retry #134`.
146
+ */
147
+ export const ReplyPostInputSchema = z.object({
148
+ run_id: z.string().min(1),
149
+ key: z.string().min(1),
150
+ reply: z.string().min(1),
151
+ })
152
+
153
+ /**
154
+ * API Response: `posted` (it went on the spec now), `already_posted` (the
155
+ * same reply was posted for this item before; nothing was posted again),
156
+ * `refused` (the board won't post it, and says why), or `failed` (`gh`
157
+ * failed). `ok` is true for the first two.
158
+ */
159
+ export const ReplyPostOutputSchema = z.object({
160
+ ok: z.boolean(),
161
+ status: z.enum(['posted', 'already_posted', 'refused', 'failed']),
162
+ message: z.string(),
163
+ posted: PostedReplySchema.nullable(),
164
+ })
165
+
166
+ export const replyPostRpc = defineRpc({
167
+ name: 'reply.post',
168
+ input: ReplyPostInputSchema,
169
+ output: ReplyPostOutputSchema,
170
+ })
171
+
172
+ export type ReplyPostInput = z.infer<typeof ReplyPostInputSchema>
173
+ export type ReplyPostOutput = z.infer<typeof ReplyPostOutputSchema>
@@ -0,0 +1,77 @@
1
+ /**
2
+ * What the board only counts: the decision model's questions, agent
3
+ * messages, and memory with its learner.
4
+ */
5
+ import { z } from 'zod'
6
+
7
+ /**
8
+ * The decision model (Clef; Jev in older journals) in shadow mode: how often
9
+ * the engine asked it and what came back. The engine never acts on the
10
+ * answers, so the board only counts them.
11
+ */
12
+ export const JevCountsSchema = z.object({
13
+ asked: z.number().int().min(0),
14
+ answered: z.number().int().min(0),
15
+ failed: z.number().int().min(0),
16
+ })
17
+
18
+ export type JevCounts = z.infer<typeof JevCountsSchema>
19
+
20
+ /**
21
+ * Agent messages: `sent` counts the queued and the not delivered ones,
22
+ * `refused` the ones the engine turned down, and `delivered` the messages
23
+ * handed over, once per receiver (a message to `all` can count more than
24
+ * once). Messages happen inside a turn, so the board only counts them.
25
+ */
26
+ export const MessageCountsSchema = z.object({
27
+ sent: z.number().int().min(0),
28
+ refused: z.number().int().min(0),
29
+ delivered: z.number().int().min(0),
30
+ })
31
+
32
+ export type MessageCounts = z.infer<typeof MessageCountsSchema>
33
+
34
+ /**
35
+ * Where the learner stands at the end of a run (#370): `waiting` until it
36
+ * starts (a run without memory never starts it), `learning`, `learned`
37
+ * once it answered, or `skipped` when the engine gave up on it.
38
+ */
39
+ export const LearnerStateSchema = z.enum([
40
+ 'waiting',
41
+ 'learning',
42
+ 'learned',
43
+ 'skipped',
44
+ ])
45
+
46
+ export type LearnerState = z.infer<typeof LearnerStateSchema>
47
+
48
+ /**
49
+ * Memory (#370), counted: the searches at recall points (and the vault
50
+ * searches among them that failed), the memories they showed agents, the
51
+ * learner, and what became of its memories. Memory changes no ticket, so
52
+ * the board only counts it.
53
+ */
54
+ export const MemoryCountsSchema = z.object({
55
+ searches: z.number().int().min(0),
56
+ search_errors: z.number().int().min(0),
57
+ shown: z.number().int().min(0),
58
+ learner: LearnerStateSchema,
59
+ added: z.number().int().min(0),
60
+ updated: z.number().int().min(0),
61
+ refused: z.number().int().min(0),
62
+ failed: z.number().int().min(0),
63
+ })
64
+
65
+ export type MemoryCounts = z.infer<typeof MemoryCountsSchema>
66
+
67
+ /** Memory before any of its records. */
68
+ export const NO_MEMORY: MemoryCounts = {
69
+ searches: 0,
70
+ search_errors: 0,
71
+ shown: 0,
72
+ learner: 'waiting',
73
+ added: 0,
74
+ updated: 0,
75
+ refused: 0,
76
+ failed: 0,
77
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The live file (#628) in the board's state: what an agent in a turn is
3
+ * doing now, from the engine's `live.json`, never from the journal.
4
+ */
5
+ import { z } from 'zod'
6
+
7
+ /**
8
+ * A live file written longer ago than this means the engine is hung or
9
+ * gone (#635). The engine rewrites it at least every 5 seconds, so a long
10
+ * tool call never gets near it.
11
+ */
12
+ export const LIVE_STALE_MS = 60_000
13
+
14
+ /**
15
+ * Whether a live file written at `written_at` is fresh at `now`: written
16
+ * at most `LIVE_STALE_MS` ago.
17
+ *
18
+ * @example
19
+ * liveIsFresh({ written_at: '2026-09-23T12:30:00.000Z', now: Date.parse('2026-09-23T12:31:10.000Z') }) // false
20
+ */
21
+ export const liveIsFresh = ({
22
+ written_at,
23
+ now,
24
+ }: {
25
+ written_at: string
26
+ now: number
27
+ }): boolean => now - Date.parse(written_at) <= LIVE_STALE_MS
28
+
29
+ /** The agent's latest action: a tool and a short target, with its time. */
30
+ export const LiveActionSchema = z.object({
31
+ tool: z.string(),
32
+ target: z.string(),
33
+ at: z.string(),
34
+ })
35
+
36
+ /** A tool call still running, since `started_at`. */
37
+ export const LiveOpenCallSchema = z.object({
38
+ tool: z.string(),
39
+ target: z.string(),
40
+ started_at: z.string(),
41
+ })
42
+
43
+ /**
44
+ * One of an agent's helpers (#636): a read-only Haiku session it started,
45
+ * with its latest action and whether it's done.
46
+ */
47
+ export const LiveHelperSchema = z.object({
48
+ model: z.string().nullable().default(null),
49
+ action: LiveActionSchema.nullable().default(null),
50
+ done: z.boolean().default(false),
51
+ })
52
+
53
+ export type LiveHelper = z.infer<typeof LiveHelperSchema>
54
+
55
+ /** One agent in a turn, as the live file has it. */
56
+ export const LiveAgentSchema = z.object({
57
+ ticket: z.number().int().nullable(),
58
+ role: z.string(),
59
+ model: z.string().nullable().default(null),
60
+ /** The turn's tokens so far, cache reads included; the journal has none of them yet. */
61
+ tokens: z.number().min(0).default(0),
62
+ action: LiveActionSchema.nullable().default(null),
63
+ open_call: LiveOpenCallSchema.nullable().default(null),
64
+ /**
65
+ * The agent's activity count for each of the last 30 seconds (#631),
66
+ * oldest first; a file from before has none.
67
+ */
68
+ activity: z.array(z.number().min(0)).catch([]).default([]),
69
+ /** The agent's helpers (#636), oldest first; a file from before has none. */
70
+ helpers: z.array(LiveHelperSchema).catch([]).default([]),
71
+ })
72
+
73
+ export type LiveAgent = z.infer<typeof LiveAgentSchema>
74
+
75
+ /** One action of the run's live feed (#634): when, who, on which model, and what. */
76
+ export const LiveFeedEntrySchema = z.object({
77
+ at: z.string(),
78
+ ticket: z.number().int().nullable(),
79
+ role: z.string(),
80
+ model: z.string().nullable().default(null),
81
+ tool: z.string(),
82
+ target: z.string(),
83
+ })
84
+
85
+ export type LiveFeedEntry = z.infer<typeof LiveFeedEntrySchema>
86
+
87
+ /**
88
+ * A wait inside a step (#632): a ticket's check waits for a prepare slot
89
+ * or a test slot, while `in_use` of the engine's `slots` are taken.
90
+ */
91
+ export const LiveWaitSchema = z.object({
92
+ ticket: z.number().int(),
93
+ for: z.enum(['prepare_slot', 'test_slot']),
94
+ in_use: z.number().int().min(0),
95
+ slots: z.number().int().min(0),
96
+ since: z.string(),
97
+ })
98
+
99
+ export type LiveWait = z.infer<typeof LiveWaitSchema>
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The final review in the board's state: its lenses, and where the review
3
+ * stands.
4
+ */
5
+ import { z } from 'zod'
6
+
7
+ import { FindingCountsSchema } from './board-state-ticket'
8
+
9
+ export const LENS_NAMES = [
10
+ 'architecture',
11
+ 'simplification',
12
+ 'security',
13
+ 'integration',
14
+ 'rules',
15
+ ] as const
16
+
17
+ export const LensNameSchema = z.enum(LENS_NAMES)
18
+
19
+ export type LensName = z.infer<typeof LensNameSchema>
20
+
21
+ export const LensStateSchema = z.enum([
22
+ 'waiting',
23
+ 'reviewing',
24
+ 'fixing',
25
+ 'clean',
26
+ ])
27
+
28
+ export type LensState = z.infer<typeof LensStateSchema>
29
+
30
+ export const LensCardSchema = z.object({
31
+ name: LensNameSchema,
32
+ state: LensStateSchema,
33
+ findings: FindingCountsSchema,
34
+ })
35
+
36
+ export type LensCard = z.infer<typeof LensCardSchema>
37
+
38
+ export const FinalReviewStateSchema = z.enum([
39
+ 'waiting',
40
+ 'reviewing',
41
+ 'fixing',
42
+ 'stuck',
43
+ 'passed',
44
+ ])
45
+
46
+ export type FinalReviewState = z.infer<typeof FinalReviewStateSchema>
47
+
48
+ export const FinalReviewSchema = z.object({
49
+ state: FinalReviewStateSchema,
50
+ /** False (drawn dimmed) until every ticket is done or skipped. */
51
+ active: z.boolean(),
52
+ round: z.number().int().min(0),
53
+ fix_round: z.number().int().min(0),
54
+ lenses: z.array(LensCardSchema),
55
+ /** What was tried on it so far, oldest first. */
56
+ tried: z.array(z.string()),
57
+ })
58
+
59
+ export type FinalReview = z.infer<typeof FinalReviewSchema>