@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,238 @@
1
+ /**
2
+ * A ticket's card in the panel: its number and title, its stage pill, the
3
+ * step dots, its agent's model and Clef's line, the fix and review counters, its tokens and tests, and its
4
+ * details when opened. With an entry in the run's live file (#628), the
5
+ * step text gives way to what its agent is doing now, and its tokens count
6
+ * up with the turn, followed by the latest action's age; a busy card's
7
+ * line ends in its heartbeat (#631), each running helper gets a line under
8
+ * it (#636), and a check waiting there for a slot (#632) reads as a wait,
9
+ * with the amber bar. In a run with no sign of life (#635), a busy or
10
+ * waiting card's step text says so, in place of any wait, its heartbeat is
11
+ * a flat red line, and its timers stop at the last sign of life. Each line stays on one line (`one-line.tsx`): the
12
+ * number, pill, dots, counters and tokens keep their size, the text beside
13
+ * them is cut, and a tap on it shows it whole in a bubble.
14
+ */
15
+ import { Icon } from '@getpaseo/plugin/client/react-native'
16
+ import { Pressable, Text, View } from 'react-native'
17
+
18
+ import { stageColor, tokensText, useNow } from './board-look'
19
+ import { Counter, StepLine } from './board-panel-parts'
20
+ import type { Look } from './board-panel-styles'
21
+ import { noLife } from './engine-life'
22
+ import { Heartbeat } from './heartbeat'
23
+ import { HelperLines } from './helper-lines'
24
+ import { liveAgeText, liveText } from './live-line'
25
+ import { ModelLine } from './model-line'
26
+ import { BubbleScope, CutLine, useBubbles } from './one-line'
27
+ import { StepDots } from './step-dots'
28
+ import { TicketDetail } from './ticket-detail'
29
+ import { slotWait, ticketWait } from './ticket-wait'
30
+ import { WaitLine } from './wait-line'
31
+
32
+ import {
33
+ planUsedText,
34
+ type BoardState,
35
+ type TicketCard,
36
+ type TicketStage,
37
+ } from '../shared/board-state'
38
+
39
+ /** The stages whose card has no heartbeat (#631), live entry or not. */
40
+ const QUIET: ReadonlySet<TicketStage> = new Set([
41
+ 'done',
42
+ 'skipped',
43
+ 'stuck',
44
+ 'blocked',
45
+ ])
46
+
47
+ export const TicketCardView = ({
48
+ ticket,
49
+ state,
50
+ open,
51
+ onToggle,
52
+ theme,
53
+ styles,
54
+ }: {
55
+ ticket: TicketCard
56
+ state: BoardState
57
+ open: boolean
58
+ onToggle: () => void
59
+ } & Look) => {
60
+ const bubbles = useBubbles()
61
+ const now = useNow()
62
+ const held = ticketWait({ state, ticket }) ?? slotWait({ ticket })
63
+ // A busy or waiting card of a run with no sign of life says so, and
64
+ // its timers stop.
65
+ const busy =
66
+ !QUIET.has(ticket.stage) &&
67
+ (ticket.live !== null || ticket.current_step !== null || held !== null)
68
+ const lifeless = busy ? noLife({ state, now }) : null
69
+ const clock = lifeless?.at ?? now
70
+ // A ticket waiting on purpose between steps, or its check waiting for
71
+ // a slot (#632), reads "waiting", in amber, while the run is alive.
72
+ const wait = lifeless ? null : held
73
+ // What its agent is doing now, from the live file, in place of the step text.
74
+ const doing = liveText({ live: ticket.live, now: clock })
75
+ const color = wait
76
+ ? theme.colors.statusWarning
77
+ : stageColor({ stage: ticket.stage, theme })
78
+ const failing =
79
+ ticket.tests &&
80
+ ticket.tests.failing > 0 &&
81
+ ticket.stage !== 'done' &&
82
+ ticket.stage !== 'skipped'
83
+ ? ticket.tests
84
+ : null
85
+ const blockers =
86
+ ticket.findings && ticket.stage !== 'done' ? ticket.findings.blocker : 0
87
+ return (
88
+ <Pressable
89
+ accessibilityRole="button"
90
+ accessibilityLabel={`Ticket ${ticket.number}, ${ticket.stage}. ${open ? 'Hide' : 'Show'} details`}
91
+ onPress={() => {
92
+ // A tap beside an open bubble only closes it.
93
+ if (bubbles.open) bubbles.show(null)
94
+ else onToggle()
95
+ }}
96
+ style={({ pressed }) => [
97
+ styles.card,
98
+ pressed ? styles.cardPressed : null,
99
+ ]}
100
+ >
101
+ <BubbleScope bubbles={bubbles} styles={styles}>
102
+ <View style={styles.cardTop}>
103
+ <Text style={styles.number} numberOfLines={1}>
104
+ #{ticket.number}
105
+ </Text>
106
+ <CutLine
107
+ text={ticket.title}
108
+ style={styles.cardTitle}
109
+ styles={styles}
110
+ />
111
+ {ticket.refactor ? (
112
+ <Text style={styles.tag} numberOfLines={1}>
113
+ refactor
114
+ </Text>
115
+ ) : null}
116
+ <View style={styles.spacer} />
117
+ <Text
118
+ style={[styles.pill, { color, borderColor: color }]}
119
+ numberOfLines={1}
120
+ >
121
+ {wait ? 'waiting' : ticket.activity}
122
+ </Text>
123
+ <Icon
124
+ name={open ? 'ChevronDown' : 'ChevronRight'}
125
+ size={14}
126
+ color={theme.colors.foregroundMuted}
127
+ />
128
+ </View>
129
+ <View style={styles.line}>
130
+ <StepDots ticket={ticket} theme={theme} styles={styles} />
131
+ {lifeless ? (
132
+ <CutLine
133
+ text={lifeless.text}
134
+ style={[
135
+ styles.text,
136
+ { color: theme.colors.statusDanger },
137
+ ]}
138
+ styles={styles}
139
+ />
140
+ ) : wait ? (
141
+ <WaitLine wait={wait} theme={theme} styles={styles} />
142
+ ) : doing || ticket.role ? (
143
+ <CutLine
144
+ text={doing ?? ticket.role ?? ''}
145
+ style={styles.text}
146
+ styles={styles}
147
+ />
148
+ ) : null}
149
+ {lifeless ||
150
+ (!wait &&
151
+ ticket.live !== null &&
152
+ !QUIET.has(ticket.stage)) ? (
153
+ <>
154
+ <View style={styles.spacer} />
155
+ <Heartbeat
156
+ live={ticket.live}
157
+ gone={lifeless !== null}
158
+ theme={theme}
159
+ styles={styles}
160
+ />
161
+ </>
162
+ ) : null}
163
+ </View>
164
+ <HelperLines
165
+ live={ticket.live}
166
+ now={clock}
167
+ theme={theme}
168
+ styles={styles}
169
+ />
170
+ {ticket.current_step && doing === null && wait === null ? (
171
+ <StepLine
172
+ step={ticket.current_step}
173
+ prefix=""
174
+ at={lifeless?.at ?? null}
175
+ styles={styles}
176
+ />
177
+ ) : null}
178
+ {ticket.model !== null && ticket.model.role === ticket.role ? (
179
+ <ModelLine
180
+ model={ticket.model}
181
+ bar={state.confidence_bar}
182
+ theme={theme}
183
+ styles={styles}
184
+ />
185
+ ) : null}
186
+ <View style={styles.line}>
187
+ <Counter
188
+ label="fix"
189
+ value={ticket.fix_round}
190
+ theme={theme}
191
+ styles={styles}
192
+ />
193
+ <Counter
194
+ label="review"
195
+ value={ticket.review_round}
196
+ theme={theme}
197
+ styles={styles}
198
+ />
199
+ <Text style={[styles.mono, styles.fixed]} numberOfLines={1}>
200
+ {`${tokensText({
201
+ tokens: ticket.tokens + (ticket.live?.tokens ?? 0),
202
+ })} tokens${liveAgeText({ live: ticket.live, now: clock })}`}
203
+ </Text>
204
+ {ticket.plan_used.length > 0 ? (
205
+ <CutLine
206
+ text={`plan: ${planUsedText({ used: ticket.plan_used, sign: '+' })}`}
207
+ style={styles.mono}
208
+ styles={styles}
209
+ />
210
+ ) : null}
211
+ {failing ? (
212
+ <CutLine
213
+ text={`${failing.failing} of ${failing.total} tests failing`}
214
+ style={[
215
+ styles.muted,
216
+ { color: theme.colors.statusDanger },
217
+ ]}
218
+ styles={styles}
219
+ />
220
+ ) : null}
221
+ {blockers > 0 ? (
222
+ <CutLine
223
+ text={`${blockers} blocker from the ticket review`}
224
+ style={[
225
+ styles.muted,
226
+ { color: theme.colors.statusWarning },
227
+ ]}
228
+ styles={styles}
229
+ />
230
+ ) : null}
231
+ </View>
232
+ </BubbleScope>
233
+ {open ? (
234
+ <TicketDetail ticket={ticket} state={state} styles={styles} />
235
+ ) : null}
236
+ </Pressable>
237
+ )
238
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * What a ticket card shows when it is open: its steps, what it waits on, its
3
+ * last tests, findings, review fix rounds, tokens, and what was tried.
4
+ */
5
+ import { Text, View } from 'react-native'
6
+
7
+ import { tokensText } from './board-look'
8
+ import type { Styles } from './board-panel-styles'
9
+ import { dotKinds } from './step-dots'
10
+
11
+ import {
12
+ LOOP_CAP,
13
+ STEP_NAMES,
14
+ type BoardState,
15
+ type TicketCard,
16
+ } from '../shared/board-state'
17
+
18
+ export const TicketDetail = ({
19
+ ticket,
20
+ state,
21
+ styles,
22
+ }: {
23
+ ticket: TicketCard
24
+ state: BoardState
25
+ styles: Styles
26
+ }) => {
27
+ const kinds = dotKinds({ ticket })
28
+ const waits = ticket.blockers.map((number) => {
29
+ const blocker = state.tickets.find((card) => card.number === number)
30
+ return `#${number} ${blocker ? blocker.stage : 'closed'}`
31
+ })
32
+ return (
33
+ <View style={styles.detail}>
34
+ <Text style={styles.muted}>
35
+ steps:{' '}
36
+ {STEP_NAMES.map(
37
+ (name, index) =>
38
+ `${name} ${kinds[index] === 'skip' ? '(not needed)' : (kinds[index] ?? '')}`
39
+ ).join(' → ')}
40
+ </Text>
41
+ <Text style={styles.muted}>
42
+ waits on: {waits.length > 0 ? waits.join(', ') : 'nothing'}
43
+ </Text>
44
+ {ticket.tests ? (
45
+ <Text style={styles.muted}>
46
+ last tests:{' '}
47
+ {ticket.tests.failing > 0
48
+ ? `${ticket.tests.failing} of ${ticket.tests.total} failing`
49
+ : `${ticket.tests.total} passing`}
50
+ </Text>
51
+ ) : null}
52
+ {ticket.findings ? (
53
+ <Text style={styles.muted}>
54
+ findings: {ticket.findings.blocker} blocker ·{' '}
55
+ {ticket.findings.should_fix} should-fix ·{' '}
56
+ {ticket.findings.nit} nit
57
+ </Text>
58
+ ) : null}
59
+ {ticket.review_fix_round > 0 ? (
60
+ <Text style={styles.muted}>
61
+ review fix rounds: {ticket.review_fix_round}/{LOOP_CAP}
62
+ </Text>
63
+ ) : null}
64
+ {Object.keys(ticket.agent_tokens).length > 0 ? (
65
+ <Text style={styles.muted}>
66
+ tokens:{' '}
67
+ {Object.entries(ticket.agent_tokens)
68
+ .map(
69
+ ([role, tokens]) =>
70
+ `${role} ${tokensText({ tokens })}`
71
+ )
72
+ .join(' · ')}
73
+ </Text>
74
+ ) : null}
75
+ {ticket.tried.length > 0 ? (
76
+ <>
77
+ <Text style={styles.label}>Tried</Text>
78
+ {ticket.tried.map((line, index) => (
79
+ <Text key={`${index}-${line}`} style={styles.muted}>
80
+ {index + 1}. {line}
81
+ </Text>
82
+ ))}
83
+ </>
84
+ ) : null}
85
+ </View>
86
+ )
87
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The tickets as a stack of stages (Blocked, Building, Reviewing, Testing,
3
+ * Fixing, Joining, Done, Skipped), each a fold of ticket cards, then the
4
+ * tickets left out for a person.
5
+ */
6
+ import type { Dispatch, SetStateAction } from 'react'
7
+
8
+ import { Text, View } from 'react-native'
9
+
10
+ import { stageColor } from './board-look'
11
+ import { Section, StageHeader } from './board-panel-parts'
12
+ import {
13
+ FOR_A_PERSON,
14
+ TICKET_STAGES,
15
+ toggled,
16
+ type Key,
17
+ } from './board-panel-stages'
18
+ import type { Look } from './board-panel-styles'
19
+ import { TicketCardView } from './ticket-card-view'
20
+
21
+ import type { BoardState, TicketCard } from '../shared/board-state'
22
+
23
+ export const TicketStack = ({
24
+ state,
25
+ folded,
26
+ setFolded,
27
+ openCards,
28
+ setOpenCards,
29
+ theme,
30
+ styles,
31
+ }: {
32
+ state: BoardState
33
+ folded: ReadonlySet<Key>
34
+ setFolded: Dispatch<SetStateAction<ReadonlySet<Key>>>
35
+ openCards: ReadonlySet<Key>
36
+ setOpenCards: Dispatch<SetStateAction<ReadonlySet<Key>>>
37
+ } & Look) => {
38
+ const look = { theme, styles }
39
+ const card = (ticket: TicketCard) => {
40
+ const key = `ticket-${ticket.number}`
41
+ return (
42
+ <TicketCardView
43
+ key={key}
44
+ ticket={ticket}
45
+ state={state}
46
+ open={openCards.has(key)}
47
+ onToggle={() => setOpenCards((set) => toggled({ set, key }))}
48
+ {...look}
49
+ />
50
+ )
51
+ }
52
+
53
+ return (
54
+ <>
55
+ {TICKET_STAGES.map((entry) => {
56
+ const tickets = state.tickets.filter(
57
+ (ticket) => ticket.stage === entry.stage
58
+ )
59
+ const isFolded = folded.has(entry.stage)
60
+ return (
61
+ <Section key={entry.stage} styles={styles}>
62
+ <StageHeader
63
+ title={entry.title}
64
+ hint={entry.hint}
65
+ icon={entry.icon}
66
+ count={tickets.length}
67
+ color={stageColor({ stage: entry.stage, theme })}
68
+ folded={isFolded}
69
+ onPress={() =>
70
+ setFolded((set) =>
71
+ toggled({ set, key: entry.stage })
72
+ )
73
+ }
74
+ {...look}
75
+ />
76
+ {tickets.length > 0 && !isFolded ? (
77
+ <View style={styles.stageBody}>
78
+ {tickets.map(card)}
79
+ </View>
80
+ ) : null}
81
+ </Section>
82
+ )
83
+ })}
84
+
85
+ {state.for_a_person.length > 0 ? (
86
+ <Section styles={styles}>
87
+ <StageHeader
88
+ title="For a person"
89
+ hint="left out of this run: a person does these, or they wait on one"
90
+ icon="User"
91
+ count={state.for_a_person.length}
92
+ color={theme.colors.foregroundMuted}
93
+ folded={folded.has(FOR_A_PERSON)}
94
+ onPress={() =>
95
+ setFolded((set) =>
96
+ toggled({ set, key: FOR_A_PERSON })
97
+ )
98
+ }
99
+ {...look}
100
+ />
101
+ {folded.has(FOR_A_PERSON) ? null : (
102
+ <View style={styles.stageBody}>
103
+ {state.for_a_person.map((ticket) => (
104
+ <Text
105
+ key={ticket.number}
106
+ style={styles.muted}
107
+ numberOfLines={2}
108
+ >
109
+ #{ticket.number} {ticket.title} ·{' '}
110
+ {ticket.why}
111
+ </Text>
112
+ ))}
113
+ </View>
114
+ )}
115
+ </Section>
116
+ ) : null}
117
+ </>
118
+ )
119
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * What a waiting card waits for (#558, #559): a ticket the engine holds on
3
+ * purpose between steps. The engine's `ticket_waiting` names the reason; an
4
+ * old journal without it gets the same reasons from the board's own rules,
5
+ * or "waiting for the engine's next step" when none fits, never a guess.
6
+ * A check waiting for a slot inside its step (#632) comes from the live file.
7
+ */
8
+ import {
9
+ BASELINE_STEP_TEXT,
10
+ type BoardState,
11
+ type TicketCard,
12
+ type TicketStage,
13
+ type WaitFor,
14
+ } from '../shared/board-state'
15
+
16
+ /** A wait as the card shows it: its text, and since when (if known). */
17
+ export type ShownWait = { text: string; since: string | null }
18
+
19
+ /** The cards that never wait: done, stuck, skipped, or blocked. */
20
+ const NEVER_WAITS = new Set<TicketStage>([
21
+ 'done',
22
+ 'stuck',
23
+ 'skipped',
24
+ 'blocked',
25
+ ])
26
+
27
+ /** What no rule explains. */
28
+ export const ENGINE_NEXT_STEP = "waiting for the engine's next step"
29
+
30
+ /**
31
+ * A wait's reason in words.
32
+ *
33
+ * @example
34
+ * waitText({ waiting_for: 'join', on_ticket: 331 }) // 'waiting to join after #331'
35
+ */
36
+ export const waitText = ({
37
+ waiting_for,
38
+ on_ticket,
39
+ }: {
40
+ waiting_for: WaitFor
41
+ on_ticket: number | null
42
+ }): string => {
43
+ const other = on_ticket === null ? null : `#${on_ticket}`
44
+ switch (waiting_for) {
45
+ case 'baseline':
46
+ return other === null
47
+ ? 'waiting for the shared baseline tests'
48
+ : `waiting for ${other}'s baseline tests`
49
+ case 'join':
50
+ return other === null
51
+ ? 'waiting to join'
52
+ : `waiting to join after ${other}`
53
+ case 'start':
54
+ return other === null
55
+ ? 'waiting to start'
56
+ : `starts once ${other} has joined`
57
+ }
58
+ }
59
+
60
+ /**
61
+ * What a ticket's check waits for inside its step (#632), from the live
62
+ * file, or `null` when it waits for no slot.
63
+ *
64
+ * @example
65
+ * slotWait({ ticket }) // { text: 'waiting for a test slot (2 of 2 in use)', since: '2026-...' }
66
+ */
67
+ export const slotWait = ({
68
+ ticket,
69
+ }: {
70
+ ticket: TicketCard
71
+ }): ShownWait | null => {
72
+ const wait = ticket.slot_wait
73
+ if (wait === null) return null
74
+ const slot = wait.for === 'prepare_slot' ? 'a prepare slot' : 'a test slot'
75
+ return {
76
+ text: `waiting for ${slot} (${wait.in_use} of ${wait.slots} in use)`,
77
+ since: wait.since,
78
+ }
79
+ }
80
+
81
+ /** An approved ticket in the join queue, not yet joining. */
82
+ const approvedWaiting = (card: TicketCard): boolean =>
83
+ card.stage === 'joining' &&
84
+ card.activity === 'approved' &&
85
+ card.current_step === null
86
+
87
+ /** The ticket joining the run branch now, other than `number`. */
88
+ const joinerBesides = ({
89
+ state,
90
+ number,
91
+ }: {
92
+ state: BoardState
93
+ number: number
94
+ }): number | null =>
95
+ state.tickets.find(
96
+ (card) =>
97
+ card.number !== number &&
98
+ card.stage === 'joining' &&
99
+ !approvedWaiting(card)
100
+ )?.number ?? null
101
+
102
+ /** The ticket running the baseline tests now, other than `number`. */
103
+ const baselineRunner = ({
104
+ state,
105
+ number,
106
+ }: {
107
+ state: BoardState
108
+ number: number
109
+ }): number | null =>
110
+ state.tickets.find(
111
+ (card) =>
112
+ card.number !== number &&
113
+ card.current_step?.text === BASELINE_STEP_TEXT
114
+ )?.number ?? null
115
+
116
+ /** #558's rules, for a started ticket between steps of an old journal. */
117
+ const guessedReason = ({
118
+ state,
119
+ ticket,
120
+ }: {
121
+ state: BoardState
122
+ ticket: TicketCard
123
+ }): string => {
124
+ const { number } = ticket
125
+ if (ticket.stage === 'building' && ticket.baseline_tests === null) {
126
+ const runner = baselineRunner({ state, number })
127
+ if (runner !== null) {
128
+ return waitText({ waiting_for: 'baseline', on_ticket: runner })
129
+ }
130
+ }
131
+ if (approvedWaiting(ticket)) {
132
+ const joiner = joinerBesides({ state, number })
133
+ if (joiner !== null) {
134
+ return waitText({ waiting_for: 'join', on_ticket: joiner })
135
+ }
136
+ }
137
+ return ENGINE_NEXT_STEP
138
+ }
139
+
140
+ /**
141
+ * What `ticket` waits for between steps, or `null` when it isn't waiting:
142
+ * it is in a step, it never waits (done, stuck, skipped, blocked), or the
143
+ * engine isn't running the run. A ticket not started yet waits only to
144
+ * start, behind a ticket in the join queue.
145
+ *
146
+ * @example
147
+ * ticketWait({ state, ticket })
148
+ * // { text: "waiting for #553's baseline tests", since: '2026-...' }
149
+ */
150
+ export const ticketWait = ({
151
+ state,
152
+ ticket,
153
+ }: {
154
+ state: BoardState
155
+ ticket: TicketCard
156
+ }): ShownWait | null => {
157
+ if (NEVER_WAITS.has(ticket.stage) || ticket.current_step !== null) {
158
+ return null
159
+ }
160
+ if (state.run.engine_ended !== null || state.run.stopped !== null) {
161
+ return null
162
+ }
163
+ const { wait } = ticket
164
+ if (wait?.for) {
165
+ return {
166
+ text: waitText({
167
+ waiting_for: wait.for,
168
+ on_ticket: wait.on_ticket,
169
+ }),
170
+ since: wait.since,
171
+ }
172
+ }
173
+ if (!ticket.started) {
174
+ const ahead =
175
+ joinerBesides({ state, number: ticket.number }) ??
176
+ state.tickets.find(approvedWaiting)?.number ??
177
+ null
178
+ return ahead === null
179
+ ? null
180
+ : {
181
+ text: waitText({ waiting_for: 'start', on_ticket: ahead }),
182
+ since: null,
183
+ }
184
+ }
185
+ // A journal with no step records never says the ticket is between steps.
186
+ if (wait === null) return null
187
+ return { text: guessedReason({ state, ticket }), since: wait.since }
188
+ }