@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,364 @@
1
+ /**
2
+ * Where an agent's answer goes in its ticket's progress: a finished agent's
3
+ * result (fix rounds, bad tests, ticket reviews), the test setup change it
4
+ * asked for, and the engine's check of `already_done` evidence.
5
+ */
6
+ import {
7
+ badTestOf,
8
+ declinedBy,
9
+ fixerAnswered,
10
+ handoffAnswered,
11
+ handoffRole,
12
+ ranCheaper,
13
+ type FinishedContent,
14
+ } from './agent-turn-replay'
15
+ import type { JournalRecord } from './journal-record'
16
+ import type { TicketProgress } from './ticket-progress'
17
+
18
+ import {
19
+ isBlocking,
20
+ type AgentRole,
21
+ type BadTestSource,
22
+ type TicketReviewResult,
23
+ } from '../agents/role-results'
24
+
25
+ /**
26
+ * A failed try for `role` on its ticket: one more of its tries used up,
27
+ * and, if its agent ran on a model cheaper than Opus, its later fresh
28
+ * agents on the ticket step up to Opus (#604). Engine failures are no try.
29
+ */
30
+ export const failedTryChange = ({
31
+ progress,
32
+ role,
33
+ }: {
34
+ progress: TicketProgress
35
+ role: AgentRole
36
+ }): Pick<TicketProgress, 'failed_tries' | 'failed_cheaper'> => ({
37
+ failed_tries: {
38
+ ...progress.failed_tries,
39
+ [role]: (progress.failed_tries[role] ?? 0) + 1,
40
+ },
41
+ failed_cheaper:
42
+ ranCheaper(progress.launched_on[role]) &&
43
+ !progress.failed_cheaper.includes(role)
44
+ ? [...progress.failed_cheaper, role]
45
+ : progress.failed_cheaper,
46
+ })
47
+
48
+ /**
49
+ * The bounce sources once `by` sends one more test back as bad (#581), each
50
+ * kept in order so a stuck detail can name who sent the first and who the
51
+ * second. They go together with `bad_test_bounces`.
52
+ */
53
+ export const bouncedBy = ({
54
+ progress,
55
+ by,
56
+ }: {
57
+ progress: TicketProgress
58
+ by: BadTestSource
59
+ }): BadTestSource[] => [...progress.bad_test_sources, by]
60
+
61
+ /**
62
+ * Where a finished agent's result goes in its ticket's progress. A result
63
+ * that answers a fix-loop follow-up counts a round and clears the failed
64
+ * check, so it runs again.
65
+ */
66
+ export const resultChange = ({
67
+ progress,
68
+ finished,
69
+ seq,
70
+ }: {
71
+ progress: TicketProgress
72
+ finished: FinishedContent
73
+ seq: number
74
+ }): Partial<TicketProgress> => {
75
+ const fix = progress.review_fix
76
+ const { rejoin, test_update } = progress
77
+ switch (finished.role) {
78
+ case 'test-writer':
79
+ // The test-writer's answer to a test update after a rebase: the
80
+ // red check stands, as for clashed tests (#489).
81
+ if (test_update !== null && test_update.result === null) {
82
+ return {
83
+ test_update: { ...test_update, result: finished.result },
84
+ }
85
+ }
86
+ // The fresh test-writer after a rebase fixed the clashed tests:
87
+ // no fix round, and the red check stands.
88
+ if (rejoin?.tests_pending) {
89
+ return { rejoin: { ...rejoin, tests_pending: false } }
90
+ }
91
+ if (fix !== null && !fix.tests_answered) {
92
+ return {
93
+ review_fix: {
94
+ ...fix,
95
+ tests_answered: true,
96
+ ...fixerAnswered({
97
+ fix,
98
+ role: 'test-writer',
99
+ responses: finished.result.finding_responses,
100
+ }),
101
+ },
102
+ }
103
+ }
104
+ if (fix !== null && handoffRole({ fix }) === 'test-writer') {
105
+ return {
106
+ review_fix: {
107
+ ...fix,
108
+ ...handoffAnswered({
109
+ fix,
110
+ role: 'test-writer',
111
+ responses: finished.result.finding_responses,
112
+ }),
113
+ },
114
+ }
115
+ }
116
+ // An `environment` answer to a failed red check reruns it with
117
+ // the earlier tests, using no fix round (#613).
118
+ if (
119
+ finished.result.outcome === 'environment' &&
120
+ progress.red_check !== null &&
121
+ !progress.red_check.ok
122
+ ) {
123
+ return {
124
+ red_check: null,
125
+ environment_reruns: progress.environment_reruns + 1,
126
+ environment: {
127
+ role: 'test-writer',
128
+ evidence: finished.result.environment?.evidence ?? '',
129
+ check: 'red_check',
130
+ failed: progress.red_check,
131
+ },
132
+ }
133
+ }
134
+ // A new answer is checked afresh if it is `already_done` (#495).
135
+ return progress.red_check === null
136
+ ? { test_writer: finished.result, already_done_check: null }
137
+ : {
138
+ test_writer: finished.result,
139
+ already_done_check: null,
140
+ red_check: null,
141
+ red_fix_rounds: progress.red_fix_rounds + 1,
142
+ }
143
+ case 'implementer': {
144
+ const { result } = finished
145
+ const bad_test = badTestOf(result)
146
+ // Its answer after the test-writer updated the tests closes the
147
+ // update; a done answer settles the bad test (#489).
148
+ if (test_update !== null && test_update.result !== null) {
149
+ return {
150
+ implementer: result,
151
+ test_update: null,
152
+ ...(bad_test === null
153
+ ? { bad_test: null, bad_test_source: null }
154
+ : {
155
+ bad_test,
156
+ bad_test_source: 'implementer',
157
+ bad_test_sources: bouncedBy({
158
+ progress,
159
+ by: 'implementer',
160
+ }),
161
+ bad_test_bounces: progress.bad_test_bounces + 1,
162
+ }),
163
+ }
164
+ }
165
+ // The implementer's answer to the clash message is no fix round.
166
+ // A bad test is kept, for the test update or the stuck text.
167
+ if (rejoin?.code_pending) {
168
+ return {
169
+ implementer: result,
170
+ rejoin: { ...rejoin, code_pending: false },
171
+ ...(bad_test === null
172
+ ? {}
173
+ : {
174
+ bad_test,
175
+ bad_test_source: 'implementer',
176
+ bad_test_sources: bouncedBy({
177
+ progress,
178
+ by: 'implementer',
179
+ }),
180
+ bad_test_bounces: progress.bad_test_bounces + 1,
181
+ }),
182
+ }
183
+ }
184
+ if (fix !== null && fix.tests_answered && !fix.code_answered) {
185
+ return {
186
+ review_fix: {
187
+ ...fix,
188
+ code_answered: true,
189
+ ...fixerAnswered({
190
+ fix,
191
+ role: 'implementer',
192
+ responses: result.finding_responses,
193
+ }),
194
+ bad_test,
195
+ },
196
+ }
197
+ }
198
+ if (fix !== null && handoffRole({ fix }) === 'implementer') {
199
+ return {
200
+ review_fix: {
201
+ ...fix,
202
+ ...handoffAnswered({
203
+ fix,
204
+ role: 'implementer',
205
+ responses: result.finding_responses,
206
+ }),
207
+ bad_test,
208
+ },
209
+ }
210
+ }
211
+ // An `environment` answer to failed gates reruns them, using no
212
+ // fix round (#613).
213
+ if (
214
+ result.outcome === 'environment' &&
215
+ progress.gates !== null &&
216
+ !progress.gates.ok
217
+ ) {
218
+ return {
219
+ gates: null,
220
+ environment_reruns: progress.environment_reruns + 1,
221
+ environment: {
222
+ role: 'implementer',
223
+ evidence: result.environment?.evidence ?? '',
224
+ check: 'gates',
225
+ failed: progress.gates,
226
+ },
227
+ }
228
+ }
229
+ const bounce: Partial<TicketProgress> =
230
+ bad_test === null
231
+ ? {}
232
+ : {
233
+ bad_test_bounces: progress.bad_test_bounces + 1,
234
+ bad_test,
235
+ bad_test_source: 'implementer',
236
+ bad_test_sources: bouncedBy({
237
+ progress,
238
+ by: 'implementer',
239
+ }),
240
+ }
241
+ const round: Partial<TicketProgress> =
242
+ progress.gates === null
243
+ ? {}
244
+ : {
245
+ gates: null,
246
+ gate_fix_rounds: progress.gate_fix_rounds + 1,
247
+ }
248
+ return { implementer: result, ...round, ...bounce }
249
+ }
250
+ case 'ticket-reviewer':
251
+ return reviewChange({ progress, review: finished.result, seq })
252
+ // Lenses review the run branch, never a ticket.
253
+ default:
254
+ return {}
255
+ }
256
+ }
257
+
258
+ /**
259
+ * A finished ticket review: counts the round, settles the last round's
260
+ * "won't fix" answers, keeps its nits, and opens a fix round when a finding
261
+ * is a blocker or a should-fix. A new fix round starts its gates, gate fix
262
+ * rounds, and fix commit afresh.
263
+ */
264
+ const reviewChange = ({
265
+ progress,
266
+ review,
267
+ seq,
268
+ }: {
269
+ progress: TicketProgress
270
+ review: TicketReviewResult
271
+ /** The review's record, for the join queue's order once it approves. */
272
+ seq: number
273
+ }): Partial<TicketProgress> => {
274
+ const review_rounds = progress.review_rounds + 1
275
+ const blocking = review.findings.filter(isBlocking)
276
+ const newNits = review.findings.filter(
277
+ (finding) =>
278
+ !isBlocking(finding) &&
279
+ !progress.nits.some(({ id }) => id === finding.id)
280
+ )
281
+ const settled: Partial<TicketProgress> = {
282
+ review,
283
+ review_rounds,
284
+ reviewed_sha: progress.commits.fix ?? progress.commits.green,
285
+ nits: [...progress.nits, ...newNits],
286
+ declined: [
287
+ ...progress.declined,
288
+ ...declinedBy({ fix: progress.review_fix, review }),
289
+ ],
290
+ }
291
+ if (blocking.length === 0) {
292
+ return { ...settled, review_fix: null, approved_seq: seq }
293
+ }
294
+ return {
295
+ ...settled,
296
+ approved_seq: null,
297
+ review_fix: {
298
+ round: review_rounds,
299
+ findings: blocking,
300
+ tests_answered: !blocking.some(({ kind }) => kind === 'test'),
301
+ code_answered: !blocking.some(({ kind }) => kind === 'code'),
302
+ responses: [],
303
+ handed_off: {},
304
+ bad_test: null,
305
+ },
306
+ gates: null,
307
+ gate_fix_rounds: 0,
308
+ leftovers: { ...progress.leftovers, fix: null },
309
+ commits: { ...progress.commits, fix: null },
310
+ commit_files: { ...progress.commit_files, fix: [] },
311
+ }
312
+ }
313
+
314
+ /** The test setup change an agent asked for in its result, if any. */
315
+ export const setupChangeOf = ({
316
+ finished,
317
+ }: {
318
+ finished: FinishedContent
319
+ }): TicketProgress['setup_change'] => {
320
+ if (finished.role !== 'test-writer' && finished.role !== 'implementer') {
321
+ return null
322
+ }
323
+ const { outcome, setup_change } = finished.result
324
+ if (outcome !== 'needs_setup_change') return null
325
+ return {
326
+ role: finished.role,
327
+ file: setup_change?.file ?? '',
328
+ reason: setup_change?.reason ?? finished.result.summary,
329
+ }
330
+ }
331
+
332
+ type AlreadyDoneCheckedContent = Extract<
333
+ JournalRecord,
334
+ { kind: 'already_done_checked' }
335
+ >['content']
336
+
337
+ /**
338
+ * The engine's check of a test-writer's `already_done` evidence (#495). A
339
+ * failed one is a failed try for the test-writer, in the session that gave
340
+ * the evidence, as if its turn had failed: the decision step sends it what
341
+ * did not check out, or makes the ticket stuck once its tries run out.
342
+ */
343
+ export const alreadyDoneCheckedChange = ({
344
+ progress,
345
+ checked,
346
+ }: {
347
+ progress: TicketProgress
348
+ checked: AlreadyDoneCheckedContent
349
+ }): Partial<TicketProgress> => {
350
+ const { ok, problems } = checked
351
+ if (ok) return { already_done_check: { ok, problems } }
352
+ const role = 'test-writer'
353
+ return {
354
+ already_done_check: { ok, problems },
355
+ agent_failure: {
356
+ role,
357
+ error: problems.map((problem) => `- ${problem}`).join('\n'),
358
+ failure: 'evidence',
359
+ session_id: progress.sessions[role] ?? null,
360
+ },
361
+ engine_failures: 0,
362
+ ...failedTryChange({ progress, role }),
363
+ }
364
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * A stuck ticket's `retry` in replay: refused, restarted from scratch, or
3
+ * resumed where it stopped with a fresh agent and fresh counts.
4
+ */
5
+ import type { JournalRecord } from './journal-record'
6
+ import { EMPTY_TICKET_PROGRESS, type TicketProgress } from './ticket-progress'
7
+
8
+ type RetriedContent = Extract<
9
+ JournalRecord,
10
+ { kind: 'ticket_retried' }
11
+ >['content']
12
+
13
+ /**
14
+ * A `retry` of a stuck ticket. `refused` leaves it stuck, waiting for the
15
+ * next reply. `restart` starts it over from scratch (its new snapshot was
16
+ * journaled just before), in its worktree reset to the run branch's tip.
17
+ * `resume` picks up where it stopped (see `resumedProgress`).
18
+ */
19
+ export const retriedChange = ({
20
+ progress,
21
+ retried,
22
+ }: {
23
+ progress: TicketProgress
24
+ retried: RetriedContent
25
+ }): Partial<TicketProgress> => {
26
+ switch (retried.mode) {
27
+ case 'refused':
28
+ return { reply: null }
29
+ case 'restart':
30
+ return {
31
+ ...EMPTY_TICKET_PROGRESS,
32
+ worktree:
33
+ progress.worktree === null || retried.base_sha === null
34
+ ? progress.worktree
35
+ : { ...progress.worktree, base_sha: retried.base_sha },
36
+ }
37
+ case 'resume':
38
+ return resumedProgress({ progress })
39
+ }
40
+ }
41
+
42
+ /**
43
+ * A stuck ticket resumed by `retry`: it picks up where it stopped, with a
44
+ * fresh agent (no session is kept) and fresh counts (fix rounds, failed
45
+ * tries, bad-test bounces, environment reruns, rebases, test updates), and keeps whatever the
46
+ * user changed in its worktree. An open test update is dropped. The step that got stuck runs again:
47
+ * - before the red commit, a fresh test-writer writes the tests;
48
+ * - before the green commit, a fresh implementer builds (on top of the run
49
+ * branch after a rebase), then the gates;
50
+ * - in a review fix round, the round starts over as round 1 with fresh
51
+ * fixers, so the user's changes are gated, committed, and re-reviewed;
52
+ * - a leftover scan, a failed install, a failed prepare command at the
53
+ * baseline, a reviewer's failed tries, a failed join, a refused undo of
54
+ * its join (#519), or a step crashes cut off just run again.
55
+ */
56
+ const resumedProgress = ({
57
+ progress,
58
+ }: {
59
+ progress: TicketProgress
60
+ }): Partial<TicketProgress> => {
61
+ const { stuck, commits, review_fix, rejoin } = progress
62
+ const base: Partial<TicketProgress> = {
63
+ stuck: null,
64
+ stuck_report: null,
65
+ reply: null,
66
+ retried: stuck,
67
+ setup_change: null,
68
+ agent_failure: null,
69
+ failed_tries: {},
70
+ engine_failures: 0,
71
+ sessions: {},
72
+ red_fix_rounds: 0,
73
+ gate_fix_rounds: 0,
74
+ environment_reruns: 0,
75
+ environment: null,
76
+ bad_test_bounces: 0,
77
+ bad_test_sources: [],
78
+ rejoins: 0,
79
+ test_updates: 0,
80
+ test_update: null,
81
+ install:
82
+ progress.install?.check?.ok === false ? null : progress.install,
83
+ prepare: progress.prepare?.ok === false ? null : progress.prepare,
84
+ leftovers: {
85
+ red: commits.red === null ? null : progress.leftovers.red,
86
+ green: commits.green === null ? null : progress.leftovers.green,
87
+ fix: commits.fix === null ? null : progress.leftovers.fix,
88
+ },
89
+ }
90
+ const reason = stuck?.reason
91
+ const reviewerFailed = progress.agent_failure?.role === 'ticket-reviewer'
92
+ if (
93
+ reason === 'leftovers_found' ||
94
+ reason === 'install_failed' ||
95
+ reason === 'prepare_failed' ||
96
+ reason === 'join_failed' ||
97
+ reason === 'join_gates_failed' ||
98
+ reason === 'undo_refused' ||
99
+ reason === 'crashed' ||
100
+ reviewerFailed
101
+ ) {
102
+ return base
103
+ }
104
+ if (commits.green === null) {
105
+ if (
106
+ rejoin === null &&
107
+ commits.red === null &&
108
+ progress.test_writer !== null
109
+ ) {
110
+ return {
111
+ ...base,
112
+ test_writer: null,
113
+ red_check: null,
114
+ red_tests: [],
115
+ already_done_check: null,
116
+ }
117
+ }
118
+ return { ...base, implementer: null, gates: null }
119
+ }
120
+ if (review_fix === null) return base
121
+ return {
122
+ ...base,
123
+ review_rounds: 1,
124
+ review_fix: {
125
+ ...review_fix,
126
+ round: 1,
127
+ tests_answered: !review_fix.findings.some(
128
+ ({ kind }) => kind === 'test'
129
+ ),
130
+ code_answered: !review_fix.findings.some(
131
+ ({ kind }) => kind === 'code'
132
+ ),
133
+ responses: [],
134
+ handed_off: {},
135
+ bad_test: null,
136
+ },
137
+ gates: null,
138
+ leftovers: { ...base.leftovers!, fix: null },
139
+ commits: { ...commits, fix: null },
140
+ commit_files: { ...progress.commit_files, fix: [] },
141
+ }
142
+ }
@@ -0,0 +1,122 @@
1
+ import { z } from 'zod'
2
+
3
+ import { ENTRY_FIELDS } from './entry-fields'
4
+
5
+ import { BadTestSchema } from '../agents/role-results'
6
+ import { GateCheckSchema, TestRunSchema } from '../gates/gate-schemas'
7
+
8
+ /**
9
+ * The journal entries of the run's worktrees: made, prepared, their
10
+ * baseline tests, reset, and removed.
11
+ */
12
+
13
+ /** A git worktree the engine made: where, on which branch, from which commit. */
14
+ const WorktreeSchema = z.object({
15
+ branch: z.string().min(1),
16
+ path: z.string().min(1),
17
+ base_sha: z.string().min(1),
18
+ })
19
+
20
+ /**
21
+ * The run branch's worktree. `changesets` is true when the repo had
22
+ * `.changeset/config.json` there, so the engine writes a changeset before
23
+ * the PR (#461).
24
+ */
25
+ export const RunBranchCreatedEntrySchema = z.object({
26
+ ...ENTRY_FIELDS,
27
+ kind: z.literal('run_branch_created'),
28
+ content: WorktreeSchema.extend({
29
+ changesets: z.boolean().default(false),
30
+ }),
31
+ })
32
+
33
+ export const TicketWorktreeCreatedEntrySchema = z.object({
34
+ ...ENTRY_FIELDS,
35
+ kind: z.literal('ticket_worktree_created'),
36
+ content: WorktreeSchema,
37
+ })
38
+
39
+ /**
40
+ * The config's prepare command (#481), run in a new ticket worktree before
41
+ * its baseline tests. Journaled only with a prepare command. A failed one
42
+ * leaves the baseline tests unrun.
43
+ */
44
+ export const BaselinePreparedEntrySchema = z.object({
45
+ ...ENTRY_FIELDS,
46
+ kind: z.literal('baseline_prepared'),
47
+ content: z.object({ check: GateCheckSchema }),
48
+ })
49
+
50
+ /**
51
+ * New untracked paths a run of the config's prepare command made in a
52
+ * checkout (#486): there after it ran, not before, such as a symlinked
53
+ * tools folder or a build file the repo's `.gitignore` misses. They are not
54
+ * an agent's work, so the engine never commits them or judges them by role
55
+ * rules, in any checkout of the run. Journaled right after the prepare run
56
+ * (at the baseline, the red check, or the gates), only when it made any.
57
+ * `ticket` is the step's, as on its `gates_run`: `null` in the final review.
58
+ *
59
+ * With `outside_links`, the paths are untracked symlinks pointing outside
60
+ * their checkout, found just before a commit rather than around a prepare
61
+ * run (#496): most likely a prepare run's from before the engine noted what
62
+ * prepare made (an older engine's run, resumed). No commit may hold such a
63
+ * link, so the engine leaves them out the same way.
64
+ */
65
+ export const PrepareMadeEntrySchema = z.object({
66
+ ...ENTRY_FIELDS,
67
+ kind: z.literal('prepare_made'),
68
+ content: z.object({
69
+ /** Worktree-relative, as git lists them (`git ls-files --others`). */
70
+ paths: z.array(z.string()),
71
+ /** `true` when found as outside-pointing links before a commit. */
72
+ outside_links: z.boolean().optional(),
73
+ }),
74
+ })
75
+
76
+ /** The tests as they stood before any agent worked on a ticket. */
77
+ export const BaselineTestsEntrySchema = z.object({
78
+ ...ENTRY_FIELDS,
79
+ kind: z.literal('baseline_tests'),
80
+ content: TestRunSchema,
81
+ })
82
+
83
+ /**
84
+ * A ticket took another ticket's baseline instead of running the tests
85
+ * again: both worktrees started from the same run-branch commit, `base_sha`.
86
+ */
87
+ export const BaselineReusedEntrySchema = z.object({
88
+ ...ENTRY_FIELDS,
89
+ kind: z.literal('baseline_reused'),
90
+ content: z.object({
91
+ from_ticket: z.number().int().positive(),
92
+ base_sha: z.string().min(1),
93
+ }),
94
+ })
95
+
96
+ /**
97
+ * The engine threw away every uncommitted change in a ticket's worktree, back
98
+ * to `sha`, so a fresh test-writer starts clean after a bad test.
99
+ */
100
+ export const WorktreeResetEntrySchema = z.object({
101
+ ...ENTRY_FIELDS,
102
+ kind: z.literal('worktree_reset'),
103
+ content: z.object({
104
+ sha: z.string().min(1),
105
+ /**
106
+ * The engine found this mapped test checks nothing (#581); a fresh
107
+ * test-writer replaces it. Left out when the implementer sent a test
108
+ * back.
109
+ */
110
+ bad_test: BadTestSchema.optional(),
111
+ }),
112
+ })
113
+
114
+ /**
115
+ * The engine removed these git worktrees at the end of a run. Their branches
116
+ * and the journal are kept.
117
+ */
118
+ export const WorktreesRemovedEntrySchema = z.object({
119
+ ...ENTRY_FIELDS,
120
+ kind: z.literal('worktrees_removed'),
121
+ content: z.object({ paths: z.array(z.string().min(1)) }),
122
+ })