@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,320 @@
1
+ /**
2
+ * A final review round as replay rebuilds it: lens results with namespaced
3
+ * finding ids, the round settling once every due lens is in, and the
4
+ * fixers' answers to its fix round.
5
+ */
6
+ import omit from 'lodash/omit'
7
+
8
+ import {
9
+ badTestOf,
10
+ declinedBy,
11
+ fixerAnswered,
12
+ handoffAnswered,
13
+ handoffRole,
14
+ type FinishedContent,
15
+ } from './agent-turn-replay'
16
+ import type { FinalFinding, FinalReviewState } from './final-review-state'
17
+ import { setupChangeOf } from './ticket-result-replay'
18
+
19
+ import {
20
+ isBlocking,
21
+ lensOf,
22
+ type Finding,
23
+ type LensName,
24
+ type LensReviewResult,
25
+ } from '../agents/role-results'
26
+
27
+ /**
28
+ * A lens's finding id, namespaced by its lens (`<lens>-<id>`), since lenses
29
+ * choose their ids on their own. An id that already starts with the lens's
30
+ * prefix is kept, so a re-review listing a finding again keeps its id.
31
+ */
32
+ export const namespacedId = ({
33
+ lens,
34
+ id,
35
+ }: {
36
+ lens: LensName
37
+ id: string
38
+ }): string => (id.startsWith(`${lens}-`) ? id : `${lens}-${id}`)
39
+
40
+ /** A lens's result with every finding and ruling id namespaced. */
41
+ const namespaced = ({
42
+ lens,
43
+ result,
44
+ }: {
45
+ lens: LensName
46
+ result: LensReviewResult
47
+ }): LensReviewResult => ({
48
+ ...result,
49
+ findings: result.findings.map((entry) => ({
50
+ ...entry,
51
+ id: namespacedId({ lens, id: entry.id }),
52
+ })),
53
+ rulings: result.rulings.map((entry) => ({
54
+ ...entry,
55
+ finding_id: namespacedId({ lens, id: entry.finding_id }),
56
+ })),
57
+ })
58
+
59
+ /**
60
+ * A fresh-pass finding's id (#548). The fresh lenses see no earlier
61
+ * findings, so an id they choose may already name a different earlier nit
62
+ * or declined finding: such an id gets `fresh-` after its lens prefix. The
63
+ * same nit found again keeps its id, so the PR lists it once.
64
+ */
65
+ const freshId = ({
66
+ lens,
67
+ finding,
68
+ earlier,
69
+ }: {
70
+ lens: LensName
71
+ finding: Finding
72
+ earlier: Finding[]
73
+ }): string => {
74
+ const clash = earlier.find(({ id }) => id === finding.id)
75
+ if (clash === undefined) return finding.id
76
+ const sameNit =
77
+ !isBlocking(clash) &&
78
+ !isBlocking(finding) &&
79
+ clash.title === finding.title &&
80
+ clash.file === finding.file
81
+ return sameNit
82
+ ? finding.id
83
+ : `${lens}-fresh-${finding.id.slice(lens.length + 1)}`
84
+ }
85
+
86
+ /**
87
+ * A lens's result, namespaced, with its finding ids kept apart from the
88
+ * earlier rounds' nits and declined findings if this round is the fresh
89
+ * pass (see `freshId`).
90
+ */
91
+ const lensResultOf = ({
92
+ review,
93
+ lens,
94
+ result,
95
+ }: {
96
+ review: FinalReviewState
97
+ lens: LensName
98
+ result: LensReviewResult
99
+ }): LensReviewResult => {
100
+ const named = namespaced({ lens, result })
101
+ if (!review.fresh) return named
102
+ const earlier = [
103
+ ...review.nits,
104
+ ...review.declined.map(({ finding }) => finding),
105
+ ]
106
+ return {
107
+ ...named,
108
+ findings: named.findings.map((finding) => ({
109
+ ...finding,
110
+ id: freshId({ lens, finding, earlier }),
111
+ })),
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Every due lens finished this round, and its slow tests ran if they were
117
+ * due (#563): the round can settle.
118
+ */
119
+ export const roundDone = ({ review }: { review: FinalReviewState }): boolean =>
120
+ review.lenses_due.every((lens) => review.results[lens] !== undefined) &&
121
+ (!review.slow_due || review.slow_tests !== null)
122
+
123
+ /**
124
+ * The final review once every due lens finished its round (and its slow
125
+ * tests ran, if due): settles the last fix round's "won't fix" answers per
126
+ * lens (see `declinedBy`), keeps the nits, and opens a fix round on the
127
+ * blocking findings of every due lens, with its fixers, gates, commit, and
128
+ * push afresh (and fresh fixer sessions). Failed slow tests open one too,
129
+ * even with no blocking finding (#563): its findings are empty, so only its
130
+ * gates (and their fix loop) run. No blocking finding and no failed slow
131
+ * test leaves `fix` null: the review is clean.
132
+ */
133
+ export const settleRound = ({
134
+ review,
135
+ }: {
136
+ review: FinalReviewState
137
+ }): FinalReviewState => {
138
+ const due = review.lenses_due.flatMap((lens) => {
139
+ const result = review.results[lens]
140
+ return result === undefined ? [] : [{ lens, result }]
141
+ })
142
+ const tagged = (lens: LensName, findings: Finding[]): FinalFinding[] =>
143
+ findings.map((entry) => ({ ...entry, lens }))
144
+ const declined = due.flatMap(({ lens, result }) =>
145
+ declinedBy({
146
+ fix:
147
+ review.fix === null
148
+ ? null
149
+ : {
150
+ ...review.fix,
151
+ findings: review.fix.findings.filter(
152
+ (entry) => entry.lens === lens
153
+ ),
154
+ },
155
+ review: result,
156
+ }).map((entry) => ({ ...entry, lens }))
157
+ )
158
+ const nits = due.flatMap(({ lens, result }) =>
159
+ tagged(
160
+ lens,
161
+ result.findings.filter((entry) => !isBlocking(entry))
162
+ )
163
+ )
164
+ const blocking = due.flatMap(({ lens, result }) =>
165
+ tagged(lens, result.findings.filter(isBlocking))
166
+ )
167
+ const settled: FinalReviewState = {
168
+ ...review,
169
+ declined: [...review.declined, ...declined],
170
+ nits: [
171
+ ...review.nits,
172
+ ...nits.filter(
173
+ (entry, index) =>
174
+ !review.nits.some(({ id }) => id === entry.id) &&
175
+ nits.findIndex(({ id }) => id === entry.id) === index
176
+ ),
177
+ ],
178
+ }
179
+ const slowFailed = review.slow_due && review.slow_tests?.ok === false
180
+ if (blocking.length === 0 && !slowFailed) return { ...settled, fix: null }
181
+ return {
182
+ ...settled,
183
+ fix: {
184
+ round: review.round,
185
+ findings: blocking,
186
+ tests_answered: !blocking.some(({ kind }) => kind === 'test'),
187
+ code_answered: !blocking.some(({ kind }) => kind === 'code'),
188
+ responses: [],
189
+ handed_off: {},
190
+ bad_test: null,
191
+ },
192
+ fixing_started: false,
193
+ sessions: omit(review.sessions, ['test-writer', 'implementer']),
194
+ gates: null,
195
+ gate_fix_rounds: 0,
196
+ leftovers: null,
197
+ commit: null,
198
+ pushed: null,
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Where a finished agent's result goes in the final review: a lens's
204
+ * result this round (settling the round once every due lens finished), or
205
+ * a fixer's answer to the open fix round. An implementer result after
206
+ * failed gates answers a gate fix round, like a ticket's.
207
+ */
208
+ export const finalResultChange = ({
209
+ review,
210
+ finished,
211
+ }: {
212
+ review: FinalReviewState
213
+ finished: FinishedContent
214
+ }): FinalReviewState => {
215
+ const lens = lensOf({ role: finished.role })
216
+ const { fix } = review
217
+ if (lens !== null && 'verdict' in finished.result) {
218
+ if (!review.lenses_due.includes(lens)) return review
219
+ // The integration lens's first round gives the PR's Summary and
220
+ // Merge Danger; a re-review's nulls keep them (#513).
221
+ const { summary_picture, merge_danger } =
222
+ finished.role === 'integration-lens'
223
+ ? finished.result
224
+ : { summary_picture: null, merge_danger: null }
225
+ const next: FinalReviewState = {
226
+ ...review,
227
+ summary_picture: summary_picture ?? review.summary_picture,
228
+ merge_danger: merge_danger ?? review.merge_danger,
229
+ results: {
230
+ ...review.results,
231
+ [lens]: lensResultOf({
232
+ review,
233
+ lens,
234
+ result: finished.result,
235
+ }),
236
+ },
237
+ }
238
+ return roundDone({ review: next })
239
+ ? settleRound({ review: next })
240
+ : next
241
+ }
242
+ // A fixer asking for a change only the owner may make sticks the final
243
+ // review on it (#617), like a ticket.
244
+ const setup_change = setupChangeOf({ finished })
245
+ if (finished.role === 'test-writer') {
246
+ if (fix === null) return review
247
+ const responses = finished.result.finding_responses
248
+ if (!fix.tests_answered) {
249
+ return {
250
+ ...review,
251
+ setup_change,
252
+ fix: {
253
+ ...fix,
254
+ tests_answered: true,
255
+ ...fixerAnswered({ fix, role: 'test-writer', responses }),
256
+ },
257
+ }
258
+ }
259
+ if (handoffRole({ fix }) !== 'test-writer') return review
260
+ return {
261
+ ...review,
262
+ setup_change,
263
+ fix: {
264
+ ...fix,
265
+ ...handoffAnswered({ fix, role: 'test-writer', responses }),
266
+ },
267
+ }
268
+ }
269
+ if (finished.role === 'implementer') {
270
+ const { result } = finished
271
+ const responses = result.finding_responses
272
+ if (fix !== null && fix.tests_answered && !fix.code_answered) {
273
+ return {
274
+ ...review,
275
+ setup_change,
276
+ fix: {
277
+ ...fix,
278
+ code_answered: true,
279
+ ...fixerAnswered({ fix, role: 'implementer', responses }),
280
+ bad_test: badTestOf(result),
281
+ },
282
+ }
283
+ }
284
+ if (fix !== null && handoffRole({ fix }) === 'implementer') {
285
+ return {
286
+ ...review,
287
+ setup_change,
288
+ fix: {
289
+ ...fix,
290
+ ...handoffAnswered({ fix, role: 'implementer', responses }),
291
+ bad_test: badTestOf(result),
292
+ },
293
+ }
294
+ }
295
+ if (review.gates === null) return review
296
+ // An `environment` answer to failed gates reruns them, using no
297
+ // gate fix round (#613).
298
+ if (result.outcome === 'environment' && !review.gates.ok) {
299
+ return {
300
+ ...review,
301
+ setup_change,
302
+ gates: null,
303
+ environment_reruns: review.environment_reruns + 1,
304
+ environment: {
305
+ role: 'implementer',
306
+ evidence: result.environment?.evidence ?? '',
307
+ check: 'gates',
308
+ failed: review.gates,
309
+ },
310
+ }
311
+ }
312
+ return {
313
+ ...review,
314
+ setup_change,
315
+ gates: null,
316
+ gate_fix_rounds: review.gate_fix_rounds + 1,
317
+ }
318
+ }
319
+ return review
320
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * The shape of the final review as replay rebuilds it, with its findings,
3
+ * fix round and failures, and the final review before it starts.
4
+ */
5
+ import type { AgentFailure, StuckReason } from './journal-record'
6
+ import type {
7
+ DeclinedFinding,
8
+ EnvironmentRerun,
9
+ ReplayedGates,
10
+ ReviewFix,
11
+ TicketProgress,
12
+ } from './ticket-progress'
13
+
14
+ import type {
15
+ AgentRole,
16
+ Finding,
17
+ LensName,
18
+ LensReviewResult,
19
+ MergeDanger,
20
+ } from '../agents/role-results'
21
+ import type { LeftoverHit } from '../gates/gate-schemas'
22
+
23
+ /** A final review finding: its id namespaced as `<lens>-<id>`, and its lens. */
24
+ export type FinalFinding = Finding & { lens: LensName }
25
+
26
+ /** A finding declined in the final review, with the lens that let it go. */
27
+ export type FinalDeclined = DeclinedFinding & { lens: LensName }
28
+
29
+ /** A rule file for the rules lens, as the engine read it (`null`: unreadable). */
30
+ export type RuleFile = { path: string; text: string | null }
31
+
32
+ /** A failed agent turn no agent of that role finished after. */
33
+ export type ReplayedAgentFailure = {
34
+ role: AgentRole
35
+ error: string
36
+ failure: AgentFailure
37
+ session_id: string | null
38
+ }
39
+
40
+ /**
41
+ * The open final review fix round: the blocking findings of every lens due
42
+ * in the round, and which fixers answered. Like a ticket's `ReviewFix`.
43
+ */
44
+ export type FinalReviewFix = Omit<ReviewFix, 'findings'> & {
45
+ findings: FinalFinding[]
46
+ }
47
+
48
+ /**
49
+ * Where the final review stands, from its records: its round, the lenses
50
+ * due, each lens's result this round, the open fix round and its fixers,
51
+ * gates, commit, and push, and what goes in the PR. Ticket-less agent,
52
+ * gate, scan, commit, and push records belong here once
53
+ * `final_review_started` is journaled.
54
+ */
55
+ export type FinalReviewState = {
56
+ /** 0 before the final review starts, then 1, 2, ... */
57
+ round: number
58
+ from_sha: string | null
59
+ head_sha: string | null
60
+ /** The files `git diff <from_sha>..<head_sha>` changes. */
61
+ files: string[]
62
+ /** The lenses due this round. */
63
+ lenses_due: LensName[]
64
+ rules: RuleFile[]
65
+ /** Each due lens's result this round, finding ids namespaced. */
66
+ results: Partial<Record<LensName, LensReviewResult>>
67
+ /** The open fix round; `null` before the first review settles or once clean. */
68
+ fix: FinalReviewFix | null
69
+ /** `final_review_fixing` is journaled for the open fix round. */
70
+ fixing_started: boolean
71
+ /** The session of each role's latest turn, for follow-ups. */
72
+ sessions: Partial<Record<AgentRole, string>>
73
+ /** Each role's failed turn, if no agent of that role finished after it. */
74
+ agent_failures: Partial<Record<AgentRole, ReplayedAgentFailure>>
75
+ /** Failed tries (agent, result, guard) per role, over the whole final review. */
76
+ failed_tries: Partial<Record<AgentRole, number>>
77
+ /** Engine failures in a row, per role. */
78
+ engine_failures: Partial<Record<AgentRole, number>>
79
+ /** The open fix round's gates, and its gate fix follow-ups. */
80
+ gates: ReplayedGates | null
81
+ gate_fix_rounds: number
82
+ /**
83
+ * The fixer's `environment` answers to failed gates (#613), over the
84
+ * whole final review, each a rerun that used no gate fix round.
85
+ */
86
+ environment_reruns: number
87
+ /** The latest `environment` answer, until the next agent answers. */
88
+ environment: EnvironmentRerun | null
89
+ /** The open fix round's leftover scan and commit. */
90
+ leftovers: LeftoverHit[] | null
91
+ commit: { sha: string; files: string[] } | null
92
+ /** The open fix round's push. */
93
+ pushed: string | null
94
+ /** This round's slow tests are due (#563): round 1, with a slow command. */
95
+ slow_due: boolean
96
+ /** A fix commit was pushed since round 1: a fresh pass is due once clean (#548). */
97
+ fix_pushed: boolean
98
+ /** This round is the fresh pass (#548), or its fix round. */
99
+ fresh: boolean
100
+ /** The fresh pass started; there is at most one per final review (#548). */
101
+ fresh_started: boolean
102
+ /** This round's slow test run (`gates_run` `final_slow`), once journaled. */
103
+ slow_tests: ReplayedGates | null
104
+ /** Every nit the lenses reported, one per id. */
105
+ nits: FinalFinding[]
106
+ declined: FinalDeclined[]
107
+ /** Every assumption a final review agent made, oldest first. */
108
+ assumptions: string[]
109
+ /**
110
+ * How many turns each final review role ran out of turns (#543), in the
111
+ * order each first did, for the PR.
112
+ */
113
+ out_of_turns: Partial<Record<AgentRole, number>>
114
+ passed: boolean
115
+ stuck: { reason: StuckReason; detail: string } | null
116
+ shipped: boolean
117
+ /**
118
+ * The commit of the edits left uncommitted at ship time (#509), not
119
+ * reviewed; `null` with none.
120
+ */
121
+ shipped_edits: { sha: string; message: string; files: string[] } | null
122
+ /**
123
+ * A change only the owner may make, asked for by a fixer (#617); the
124
+ * final review is stuck on it until a `retry`.
125
+ */
126
+ setup_change: TicketProgress['setup_change']
127
+ /** The fixer whose turn a crash cut off; cleared once a fixer starts. */
128
+ crashed_turn: AgentRole | null
129
+ /**
130
+ * The PR's Summary picture, from the integration lens (#513). It is kept
131
+ * across rounds; a later non-null answer replaces it.
132
+ */
133
+ summary_picture: string | null
134
+ /**
135
+ * The PR's Merge Danger, from the integration lens (#513). It is kept
136
+ * across rounds; a later non-null answer replaces it.
137
+ */
138
+ merge_danger: MergeDanger | null
139
+ }
140
+
141
+ /** The final review before it starts. */
142
+ export const EMPTY_FINAL_REVIEW: FinalReviewState = {
143
+ round: 0,
144
+ from_sha: null,
145
+ head_sha: null,
146
+ files: [],
147
+ lenses_due: [],
148
+ rules: [],
149
+ results: {},
150
+ fix: null,
151
+ fixing_started: false,
152
+ sessions: {},
153
+ agent_failures: {},
154
+ failed_tries: {},
155
+ engine_failures: {},
156
+ gates: null,
157
+ gate_fix_rounds: 0,
158
+ environment_reruns: 0,
159
+ environment: null,
160
+ leftovers: null,
161
+ commit: null,
162
+ pushed: null,
163
+ slow_due: false,
164
+ fix_pushed: false,
165
+ fresh: false,
166
+ fresh_started: false,
167
+ slow_tests: null,
168
+ nits: [],
169
+ declined: [],
170
+ assumptions: [],
171
+ out_of_turns: {},
172
+ passed: false,
173
+ stuck: null,
174
+ shipped: false,
175
+ shipped_edits: null,
176
+ setup_change: null,
177
+ crashed_turn: null,
178
+ summary_picture: null,
179
+ merge_danger: null,
180
+ }
@@ -0,0 +1,81 @@
1
+ import { z } from 'zod'
2
+
3
+ import { ENTRY_FIELDS } from './entry-fields'
4
+
5
+ import {
6
+ GateCheckSchema,
7
+ RedCheckResultSchema,
8
+ TestCaseRefSchema,
9
+ TestCaseSchema,
10
+ TestRunSchema,
11
+ } from '../gates/gate-schemas'
12
+
13
+ /**
14
+ * The journal entries of the checks on a ticket or the run branch: the red
15
+ * check, the dependency install, and the gates.
16
+ */
17
+
18
+ /**
19
+ * A lone rerun (#544): the check ran again while no other test run went,
20
+ * holding every test slot, after its old tests failed in another ticket
21
+ * too. Left out on every other check.
22
+ */
23
+ const EXCLUSIVE_FIELD = { exclusive: z.boolean().optional() }
24
+
25
+ export const RedCheckEntrySchema = z.object({
26
+ ...ENTRY_FIELDS,
27
+ kind: z.literal('red_check'),
28
+ content: RedCheckResultSchema.extend({
29
+ tests: TestRunSchema,
30
+ ...EXCLUSIVE_FIELD,
31
+ }),
32
+ })
33
+
34
+ /** Where the gates or the install ran: a ticket's worktree, or the run branch. */
35
+ export const GateTargetSchema = z.enum(['ticket', 'run_branch'])
36
+
37
+ export type GateTarget = z.infer<typeof GateTargetSchema>
38
+
39
+ /**
40
+ * Where a `gates_run` ran: a ticket's worktree, the run branch, or the
41
+ * final review's slow tests on the run branch (#563), which run once
42
+ * round 1's lenses are in and are not the fix round's gates.
43
+ */
44
+ const GatesRunTargetSchema = z.enum([...GateTargetSchema.options, 'final_slow'])
45
+
46
+ /**
47
+ * The engine installed the dependencies from the lockfile, without changing
48
+ * it, in a new ticket worktree or the run branch's checkout, before any agent
49
+ * or gate ran there. `check` is `null` when there is no `package.json`, so
50
+ * nothing to install. The run branch's record has no ticket.
51
+ */
52
+ export const DependenciesInstalledEntrySchema = z.object({
53
+ ...ENTRY_FIELDS,
54
+ kind: z.literal('dependencies_installed'),
55
+ content: z.object({
56
+ target: GateTargetSchema,
57
+ check: GateCheckSchema.nullable(),
58
+ }),
59
+ })
60
+
61
+ export const GatesRunEntrySchema = z.object({
62
+ ...ENTRY_FIELDS,
63
+ kind: z.literal('gates_run'),
64
+ content: z.object({
65
+ target: GatesRunTargetSchema,
66
+ ok: z.boolean(),
67
+ checks: z.array(GateCheckSchema),
68
+ /**
69
+ * A ticket's gates only: the test cases of the ticket's red test
70
+ * files, with their `expect()` counts (#581). Missing in old journals.
71
+ */
72
+ cases: z.array(TestCaseSchema).optional(),
73
+ /**
74
+ * A ticket's and a join's gates only: the test cases that failed in
75
+ * any bun run (#544), so a test failing in two tickets is spotted.
76
+ * Left out with none, and in old journals.
77
+ */
78
+ failed_cases: z.array(TestCaseRefSchema).optional(),
79
+ ...EXCLUSIVE_FIELD,
80
+ }),
81
+ })
@@ -0,0 +1,87 @@
1
+ import { z } from 'zod'
2
+
3
+ import { ENTRY_FIELDS } from './entry-fields'
4
+
5
+ import {
6
+ IntakeProblemSchema,
7
+ IntakeReadSchema,
8
+ LeftOutTicketSchema,
9
+ SpecSnapshotSchema,
10
+ TicketSnapshotSchema,
11
+ } from '../intake/intake-schemas'
12
+
13
+ /**
14
+ * The journal entries of intake: what it read, a refusal, nothing to do,
15
+ * and the spec and ticket snapshots.
16
+ */
17
+
18
+ export const IntakeReadEntrySchema = z.object({
19
+ ...ENTRY_FIELDS,
20
+ kind: z.literal('intake_read'),
21
+ content: IntakeReadSchema,
22
+ })
23
+
24
+ /**
25
+ * A label the refusal couldn't add or remove (#500), with the tracker's
26
+ * error. The refusal goes on without it.
27
+ */
28
+ export const LabelFailureSchema = z.object({
29
+ ticket: z.number().int().positive(),
30
+ label: z.string(),
31
+ change: z.enum(['add', 'remove']),
32
+ error: z.string(),
33
+ })
34
+
35
+ export type LabelFailure = z.infer<typeof LabelFailureSchema>
36
+
37
+ /**
38
+ * Intake refused the run. `label_failures` are the labels it couldn't
39
+ * move; older journals have none.
40
+ */
41
+ export const IntakeRefusedEntrySchema = z.object({
42
+ ...ENTRY_FIELDS,
43
+ kind: z.literal('intake_refused'),
44
+ content: z.object({
45
+ problems: z.array(IntakeProblemSchema),
46
+ label_failures: z.array(LabelFailureSchema).default([]),
47
+ }),
48
+ })
49
+
50
+ /**
51
+ * The run ends with nothing to do: at intake, as every ticket of the spec
52
+ * is closed, or after it, as every ticket's work was already on the base
53
+ * branch (`already_done`, #484; the engine closed those tickets). At
54
+ * intake, `left_out` names the open tickets left for a person (#499), when
55
+ * those are all that is open. Older journals have none.
56
+ */
57
+ export const NothingToDoEntrySchema = z.object({
58
+ ...ENTRY_FIELDS,
59
+ kind: z.literal('nothing_to_do'),
60
+ content: z.object({
61
+ closed_tickets: z.array(z.number().int()),
62
+ already_done: z.array(z.number().int().positive()).default([]),
63
+ left_out: z.array(LeftOutTicketSchema).default([]),
64
+ }),
65
+ })
66
+
67
+ export const SpecSnapshotEntrySchema = z.object({
68
+ ...ENTRY_FIELDS,
69
+ kind: z.literal('spec_snapshot'),
70
+ content: z.object({
71
+ spec: SpecSnapshotSchema,
72
+ /** The open tickets in blocker order; one `ticket_snapshot` each. */
73
+ ticket_order: z.array(z.number().int().positive()),
74
+ closed_tickets: z.array(z.number().int().positive()),
75
+ /**
76
+ * The open tickets left out for a person, and why (#499). The run
77
+ * doesn't build them or touch them. Older journals have none.
78
+ */
79
+ left_out: z.array(LeftOutTicketSchema).default([]),
80
+ }),
81
+ })
82
+
83
+ export const TicketSnapshotEntrySchema = z.object({
84
+ ...ENTRY_FIELDS,
85
+ kind: z.literal('ticket_snapshot'),
86
+ content: TicketSnapshotSchema,
87
+ })