@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,83 @@
1
+ /**
2
+ * A writer's `environment` answer (#613): when its rerun makes the ticket
3
+ * (or the final review) stuck as `environment_failed`, and the stuck
4
+ * detail, with the agent's evidence and the clipped output. Pure.
5
+ */
6
+ import { MAX_ENVIRONMENT_RERUNS } from './loop-caps'
7
+
8
+ import type {
9
+ EnvironmentRerun,
10
+ ReplayedGates,
11
+ ReplayedRedCheck,
12
+ } from '../journal/replay'
13
+ import { clipOutput } from '../shell/run-command'
14
+
15
+ /** A red check or a gate run. */
16
+ type CheckRun = ReplayedRedCheck | ReplayedGates
17
+
18
+ /**
19
+ * Longest output kept per failed check in the stuck detail, so the stuck
20
+ * comment shows its start and its end.
21
+ */
22
+ const OUTPUT_MAX = 2000
23
+
24
+ /** How a check failed: the red check's problems, or the failed gates' names. */
25
+ const failureKey = (run: CheckRun): string =>
26
+ 'checks' in run
27
+ ? run.checks
28
+ .filter(({ ok }) => !ok)
29
+ .map(({ name }) => name)
30
+ .sort()
31
+ .join('\n')
32
+ : [...run.problems].sort().join('\n')
33
+
34
+ /** The failure, with its clipped output. */
35
+ const failureText = (run: CheckRun): string =>
36
+ 'checks' in run
37
+ ? run.checks
38
+ .filter(({ ok }) => !ok)
39
+ .map(
40
+ ({ name, output }) =>
41
+ `${name} failed:\n${clipOutput({ text: output, max: OUTPUT_MAX })}`
42
+ )
43
+ .join('\n\n')
44
+ : [
45
+ `The red check's problems:\n${run.problems.map((problem) => `- ${problem}`).join('\n')}`,
46
+ `Test output:\n${clipOutput({ text: run.output, max: OUTPUT_MAX })}`,
47
+ ].join('\n\n')
48
+
49
+ /**
50
+ * Why an `environment` answer leaves the work stuck, or `null` to go on:
51
+ * its rerun is past the cap of `MAX_ENVIRONMENT_RERUNS`, or `latest`, the
52
+ * rerun, failed the same way (the same problems, or the same failed
53
+ * gates). `latest` is `null` while the rerun is due.
54
+ */
55
+ export const environmentFailedDetail = ({
56
+ environment,
57
+ reruns,
58
+ latest,
59
+ }: {
60
+ environment: EnvironmentRerun | null
61
+ /** `environment` answers so far, this one included. */
62
+ reruns: number
63
+ latest: CheckRun | null
64
+ }): string | null => {
65
+ if (environment === null) return null
66
+ const answer = `The ${environment.role} answered "environment": ${environment.evidence}`
67
+ if (latest === null) {
68
+ if (reruns <= MAX_ENVIRONMENT_RERUNS) return null
69
+ return [
70
+ answer,
71
+ `That is past the cap of ${MAX_ENVIRONMENT_RERUNS} environment reruns, so the check did not run again. It failed like this:`,
72
+ failureText(environment.failed),
73
+ ].join('\n\n')
74
+ }
75
+ if (latest.ok || failureKey(latest) !== failureKey(environment.failed)) {
76
+ return null
77
+ }
78
+ return [
79
+ answer,
80
+ 'The engine reran the check, and it failed the same way:',
81
+ failureText(latest),
82
+ ].join('\n\n')
83
+ }
@@ -0,0 +1,84 @@
1
+ import type { TicketProgress, ReplayedSnapshot } from '../journal/replay'
2
+ import { REFACTOR_LABEL } from '../tracker/tracker'
3
+
4
+ /** The most test names one ticket's Evidence line lists (#513). */
5
+ export const MAX_EVIDENCE_TESTS = 5
6
+
7
+ /** The longest test name an Evidence line shows; a longer one is clipped. */
8
+ export const MAX_EVIDENCE_NAME = 80
9
+
10
+ const EVIDENCE_INTRO =
11
+ "Each ticket's new tests failed before its code was written (the red check), and pass now (the gates)."
12
+
13
+ const clipName = (name: string): string =>
14
+ name.length > MAX_EVIDENCE_NAME
15
+ ? `${name.slice(0, MAX_EVIDENCE_NAME - 1)}…`
16
+ : name
17
+
18
+ /** Up to `MAX_EVIDENCE_TESTS` names, then how many more. */
19
+ const namesText = (names: string[]): string => {
20
+ const shown = names.slice(0, MAX_EVIDENCE_TESTS).map(clipName)
21
+ const more = names.length - shown.length
22
+ return more > 0 ? `${shown.join(', ')}, and ${more} more` : shown.join(', ')
23
+ }
24
+
25
+ /** One ticket's Evidence line. */
26
+ const evidenceLine = ({
27
+ number,
28
+ progress,
29
+ refactor,
30
+ }: {
31
+ number: number
32
+ progress: TicketProgress | undefined
33
+ refactor: boolean
34
+ }): string => {
35
+ if ((progress?.already_done ?? null) !== null) {
36
+ return `- #${number}: already done on the base branch, so no new tests.`
37
+ }
38
+ if (refactor) {
39
+ return `- #${number}: a refactor, so no new tests; the old tests still pass.`
40
+ }
41
+ const red_tests = progress?.red_tests ?? []
42
+ if (red_tests.length === 0) return `- #${number}: no red check recorded.`
43
+ const one = red_tests.length === 1
44
+ const count = `${red_tests.length} new ${one ? 'test' : 'tests'} failed first`
45
+ const passed =
46
+ progress?.gates?.ok === true || progress?.join_gates?.ok === true
47
+ const how = passed
48
+ ? `, now ${one ? 'passes' : 'pass'}`
49
+ : `; the gates have not passed ${one ? 'it' : 'them'} yet`
50
+ return `- #${number}: ${count}${how}: ${namesText(red_tests.map(({ name }) => name))}`
51
+ }
52
+
53
+ /**
54
+ * The PR's Evidence section (#513), from the journal alone: for each ticket
55
+ * in the run (not the skipped ones), the new tests its red check proved
56
+ * fail first, and whether the gates pass them now. An already-done ticket
57
+ * and a refactor ticket say why they have no new tests. Each line names at
58
+ * most `MAX_EVIDENCE_TESTS` tests. With no ticket in the run, `''`.
59
+ *
60
+ * @example
61
+ * evidenceSection({ snapshot, tickets })
62
+ * // "## Evidence\n\n...\n\n- #11: 1 new test failed first, now passes: sum adds two numbers"
63
+ */
64
+ export const evidenceSection = ({
65
+ snapshot,
66
+ tickets,
67
+ }: {
68
+ snapshot: ReplayedSnapshot
69
+ tickets: Record<number, TicketProgress>
70
+ }): string => {
71
+ const lines = snapshot.ticket_order
72
+ .filter((number) => (tickets[number]?.skipped ?? null) === null)
73
+ .map((number) =>
74
+ evidenceLine({
75
+ number,
76
+ progress: tickets[number],
77
+ refactor:
78
+ snapshot.tickets[number]?.labels.includes(REFACTOR_LABEL) ??
79
+ false,
80
+ })
81
+ )
82
+ if (lines.length === 0) return ''
83
+ return `## Evidence\n\n${EVIDENCE_INTRO}\n\n${lines.join('\n')}`
84
+ }
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Carries out one action: sends it to the module for its kind of step, or
3
+ * journals it straight away.
4
+ */
5
+ import { basename, dirname } from 'node:path'
6
+
7
+ import type { EngineAction } from './decide'
8
+ import { isFinalReviewAction } from './decide-final-review'
9
+ import { isMemoryAction } from './decide-memory'
10
+ import { executeAlreadyDoneAction } from './execute-already-done'
11
+ import {
12
+ buildContext,
13
+ executeBuildAction,
14
+ type BuildDeps,
15
+ } from './execute-build'
16
+ import { writeChangeset } from './execute-changeset'
17
+ import {
18
+ executeFinalReviewAction,
19
+ shipFinalReviewIn,
20
+ } from './execute-final-review'
21
+ import { readIntake, refuseIntake } from './execute-intake'
22
+ import { executeMemoryAction } from './execute-memory'
23
+ import { openPullRequest } from './execute-pull-request'
24
+ import {
25
+ executeRepoWaitAction,
26
+ type GoingRunsReader,
27
+ } from './execute-repo-wait'
28
+ import { executeStuckAction } from './execute-stuck'
29
+ import { executePlanAction, executeUsageLineAction } from './execute-waits'
30
+
31
+ import type { Journal } from '../journal/journal'
32
+ import { SYSTEM_CLOCK, type EngineClock } from '../limits/limit-wait'
33
+ import {
34
+ DEFAULT_USAGE_LINES,
35
+ LINE_OF,
36
+ type UsageLines,
37
+ } from '../limits/usage-line'
38
+ import type { MemoryDeps } from '../memory/memory-client'
39
+ import type { CommentStep } from '../tracker/post-comment-once'
40
+ import type { Tracker } from '../tracker/tracker'
41
+
42
+ /**
43
+ * Which try of its step an action is: the seq of the step's first try (its
44
+ * first `step_started`), and whether this try redoes one a crash cut off.
45
+ * A redo adopts the side effects its first try left behind: its comments,
46
+ * its PR, its worktrees and commits.
47
+ */
48
+ export type StepTry = { first_seq: number; redo: boolean }
49
+
50
+ /**
51
+ * Carries out one action: talks to the tracker, then records what happened in
52
+ * the journal. The only impure half of the engine; `decide` picks the action.
53
+ *
54
+ * Stop actions (`done`, `invalid_journal`) do nothing here.
55
+ */
56
+ export const executeAction = async ({
57
+ action,
58
+ journal,
59
+ tracker,
60
+ build,
61
+ clock,
62
+ reply_poll_ms,
63
+ memory,
64
+ step,
65
+ read_usage_lines,
66
+ going_runs,
67
+ }: {
68
+ action: EngineAction
69
+ journal: Journal
70
+ tracker: Tracker
71
+ /** Needed for every step after intake. */
72
+ build?: BuildDeps
73
+ /** Limit waits and reply waits sleep by it. Defaults to `SYSTEM_CLOCK`. */
74
+ clock?: EngineClock
75
+ /** How long a reply wait sleeps before it reads the spec issue. */
76
+ reply_poll_ms?: number
77
+ /** The memory client, for a run with memory on (#370). */
78
+ memory?: MemoryDeps
79
+ /**
80
+ * Which try of its step this is, from the scheduler. Left out, it is a
81
+ * first try, numbered after the journal's last record.
82
+ */
83
+ step?: StepTry
84
+ /**
85
+ * Reads the `luca-board` usage lines, again before each nap of a
86
+ * usage-line wait. Left out, the lines never change during a wait.
87
+ */
88
+ read_usage_lines?: () => Promise<UsageLines>
89
+ /**
90
+ * Lists the unfinished runs, again before each nap of a repo wait.
91
+ * Left out, no other run is going.
92
+ */
93
+ going_runs?: GoingRunsReader
94
+ }): Promise<void> => {
95
+ const tried: CommentStep = {
96
+ run_id: basename(dirname(journal.file)),
97
+ ...(step ?? {
98
+ first_seq: (journal.read().at(-1)?.seq ?? 0) + 1,
99
+ redo: false,
100
+ }),
101
+ }
102
+ if (isMemoryAction(action)) {
103
+ return executeMemoryAction({
104
+ action,
105
+ journal,
106
+ tracker,
107
+ memory,
108
+ build,
109
+ step: tried,
110
+ })
111
+ }
112
+ switch (action.type) {
113
+ case 'report_stuck':
114
+ case 'wait_for_reply':
115
+ case 'take_reply':
116
+ case 'ignore_reply':
117
+ case 'skip_ticket':
118
+ case 'report_final_review_stuck':
119
+ case 'retry_final_review':
120
+ case 'mark_run_stuck':
121
+ case 'report_run_stuck':
122
+ return executeStuckAction({
123
+ action,
124
+ journal,
125
+ tracker,
126
+ clock: clock ?? SYSTEM_CLOCK,
127
+ reply_poll_ms,
128
+ step: tried,
129
+ })
130
+ case 'stop_for_crashes':
131
+ journal.append({
132
+ kind: 'run_stopped',
133
+ ticket: null,
134
+ role: null,
135
+ content: {
136
+ reason: action.reason,
137
+ role: null,
138
+ billing: false,
139
+ crashed: true,
140
+ },
141
+ })
142
+ return
143
+ case 'record_usage':
144
+ journal.append({
145
+ kind: 'usage_recorded',
146
+ ticket: action.usage.ticket,
147
+ role: null,
148
+ content: action.usage,
149
+ })
150
+ return
151
+ case 'start_limit_wait':
152
+ case 'wait_for_limit':
153
+ case 'stop_for_billing':
154
+ return executePlanAction({
155
+ action,
156
+ journal,
157
+ tracker,
158
+ clock: clock ?? SYSTEM_CLOCK,
159
+ step: tried,
160
+ })
161
+ case 'start_usage_line_wait':
162
+ case 'wait_for_usage_line':
163
+ return executeUsageLineAction({
164
+ action,
165
+ journal,
166
+ tracker,
167
+ clock: clock ?? SYSTEM_CLOCK,
168
+ read_usage_lines:
169
+ read_usage_lines ??
170
+ (async () => ({
171
+ ...DEFAULT_USAGE_LINES,
172
+ [LINE_OF[action.window]]: action.line,
173
+ })),
174
+ step: tried,
175
+ })
176
+ case 'start_repo_wait':
177
+ case 'wait_for_repo':
178
+ return executeRepoWaitAction({
179
+ action,
180
+ journal,
181
+ tracker,
182
+ clock: clock ?? SYSTEM_CLOCK,
183
+ going_runs,
184
+ step: tried,
185
+ })
186
+ case 'read_intake':
187
+ return readIntake({
188
+ spec_number: action.spec_number,
189
+ journal,
190
+ tracker,
191
+ })
192
+ case 'refuse_intake':
193
+ return refuseIntake({
194
+ spec_number: action.spec_number,
195
+ problems: action.problems,
196
+ journal,
197
+ tracker,
198
+ step: tried,
199
+ })
200
+ case 'mark_already_done':
201
+ case 'close_ticket':
202
+ return executeAlreadyDoneAction({
203
+ action,
204
+ journal,
205
+ tracker,
206
+ step: tried,
207
+ })
208
+ case 'finish_nothing_to_do':
209
+ journal.append({
210
+ kind: 'nothing_to_do',
211
+ ticket: null,
212
+ role: null,
213
+ content: {
214
+ closed_tickets: action.closed_tickets,
215
+ already_done: action.already_done,
216
+ left_out: action.left_out ?? [],
217
+ },
218
+ })
219
+ return
220
+ case 'snapshot_intake': {
221
+ const { spec, tickets, closed_tickets, left_out } = action.snapshot
222
+ journal.append({
223
+ kind: 'spec_snapshot',
224
+ ticket: spec.number,
225
+ role: null,
226
+ content: {
227
+ spec,
228
+ ticket_order: tickets.map((ticket) => ticket.number),
229
+ closed_tickets,
230
+ left_out,
231
+ },
232
+ })
233
+ for (const ticket of tickets) {
234
+ journal.append({
235
+ kind: 'ticket_snapshot',
236
+ ticket: ticket.number,
237
+ role: null,
238
+ content: ticket,
239
+ })
240
+ }
241
+ return
242
+ }
243
+ case 'invalid_journal':
244
+ case 'done':
245
+ return
246
+ default:
247
+ if (build === undefined) {
248
+ throw new Error(
249
+ `The engine needs a git adapter and an agent launcher to ${action.type}.`
250
+ )
251
+ }
252
+ if (isFinalReviewAction(action)) {
253
+ return executeFinalReviewAction({
254
+ action,
255
+ context: buildContext({
256
+ journal,
257
+ tracker,
258
+ step: tried,
259
+ ...build,
260
+ }),
261
+ })
262
+ }
263
+ if (action.type === 'write_changeset') {
264
+ return writeChangeset({
265
+ action,
266
+ context: buildContext({
267
+ journal,
268
+ tracker,
269
+ step: tried,
270
+ ...build,
271
+ }),
272
+ })
273
+ }
274
+ if (action.type === 'open_pull_request') {
275
+ return openPullRequest({
276
+ action,
277
+ context: buildContext({
278
+ journal,
279
+ tracker,
280
+ step: tried,
281
+ ...build,
282
+ }),
283
+ })
284
+ }
285
+ if (action.type === 'ship_final_review') {
286
+ return shipFinalReviewIn({
287
+ context: buildContext({
288
+ journal,
289
+ tracker,
290
+ step: tried,
291
+ ...build,
292
+ }),
293
+ })
294
+ }
295
+ return executeBuildAction({
296
+ action,
297
+ journal,
298
+ tracker,
299
+ step: tried,
300
+ ...build,
301
+ })
302
+ }
303
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The `check_already_done` step: a test-writer's `already_done` evidence
3
+ * checked in the ticket's worktree, journaled as `already_done_checked`.
4
+ */
5
+ import { existsSync } from 'node:fs'
6
+ import { join } from 'node:path'
7
+
8
+ import uniq from 'lodash/uniq'
9
+ import uniqBy from 'lodash/uniqBy'
10
+
11
+ import {
12
+ reportFile,
13
+ testFilesIn,
14
+ ticketWorktree,
15
+ type BuildContext,
16
+ } from './build-context'
17
+ import type { BuildAction } from './decide-build'
18
+ import { leaveOutIn, ticketChanges, watchPrepare } from './worktree-changes'
19
+
20
+ import {
21
+ bunTestCommands,
22
+ hasSlowTests,
23
+ testTimeoutOf,
24
+ } from '../config/engine-config'
25
+ import { checkAlreadyDone } from '../gates/already-done-check'
26
+ import { prepareCheck } from '../gates/gate-runner'
27
+ import {
28
+ fileTestsCommand,
29
+ onlyTestFiles,
30
+ runBunTests,
31
+ } from '../gates/test-runner'
32
+
33
+ /**
34
+ * Checks a test-writer's `already_done` evidence (#495) in the ticket's
35
+ * worktree, then journals the check (`already_done_checked`): where git
36
+ * finds each named commit, next to the commit the worktree started from,
37
+ * and a run of just the named test files that exist, are test files, and
38
+ * are as they are on the base, after the prepare command if the config has
39
+ * one. With a slow command (#564), the named files, slow ones included, run
40
+ * with plain `bun test`. What the check means is `checkAlreadyDone`'s; replay
41
+ * makes a failed one a failed try for the test-writer.
42
+ */
43
+ export const checkAlreadyDoneIn = async ({
44
+ context,
45
+ action,
46
+ }: {
47
+ context: BuildContext
48
+ action: Extract<BuildAction, { type: 'check_already_done' }>
49
+ }) => {
50
+ const { path } = ticketWorktree({
51
+ state: context.state,
52
+ ticket: action.ticket,
53
+ })
54
+ const commits = []
55
+ for (const { sha } of uniqBy(action.done_by, 'sha')) {
56
+ const place = await context.git.commitOnBase({
57
+ cwd: path,
58
+ sha,
59
+ base: action.base_sha,
60
+ })
61
+ commits.push({ sha, place })
62
+ }
63
+ const files = uniq(
64
+ action.criteria.flatMap(({ tests }) => tests.map(({ file }) => file))
65
+ )
66
+ const missing = files.filter((file) => !existsSync(join(path, file)))
67
+ const test_files = await testFilesIn({ context, cwd: path })
68
+ const changed = (
69
+ await ticketChanges({
70
+ context,
71
+ cwd: path,
72
+ leave_out: await leaveOutIn({
73
+ context,
74
+ cwd: path,
75
+ ticket: action.ticket,
76
+ }),
77
+ })
78
+ ).map((change) => change.path)
79
+ const runnable = files.filter(
80
+ (file) =>
81
+ !missing.includes(file) &&
82
+ test_files.includes(file) &&
83
+ !changed.includes(file)
84
+ )
85
+ const prepare =
86
+ runnable.length === 0
87
+ ? null
88
+ : await watchPrepare({
89
+ context,
90
+ cwd: path,
91
+ ticket: action.ticket,
92
+ run: () =>
93
+ prepareCheck({ config: context.config, cwd: path }),
94
+ })
95
+ const run =
96
+ runnable.length === 0 || (prepare !== null && !prepare.ok)
97
+ ? null
98
+ : await runBunTests({
99
+ cwd: path,
100
+ // With a slow command, the fast command would leave out
101
+ // the slow files named (#564): plain `bun test` runs all.
102
+ commands: hasSlowTests({ config: context.config })
103
+ ? [fileTestsCommand({ files: runnable })]
104
+ : bunTestCommands(context).map((command) =>
105
+ onlyTestFiles({ command, files: runnable })
106
+ ),
107
+ test_files: runnable,
108
+ timeout_ms: testTimeoutOf({ config: context.config }),
109
+ test_concurrency: context.config.test_concurrency,
110
+ report_file: await reportFile({
111
+ context,
112
+ ticket: action.ticket,
113
+ label: 'already-done',
114
+ }),
115
+ })
116
+ const { ok, problems } = checkAlreadyDone({
117
+ base_sha: action.base_sha,
118
+ criteria_ids: action.criteria_ids,
119
+ criteria: action.criteria,
120
+ commits,
121
+ missing,
122
+ test_files,
123
+ changed,
124
+ prepare,
125
+ run,
126
+ })
127
+ context.journal.append({
128
+ kind: 'already_done_checked',
129
+ ticket: action.ticket,
130
+ role: null,
131
+ content: { ok, problems, base_sha: action.base_sha, tests: run },
132
+ })
133
+ }