@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,48 @@
1
+ import { mkdir, symlink } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+
4
+ import { $ } from 'bun'
5
+
6
+ /** The folders a skill helper can be started from, for one repo and none. */
7
+ export type Checkouts = {
8
+ /** The main checkout: where a run's `run_started` repo points. */
9
+ main: string
10
+ /** A `git worktree add` of the main checkout. */
11
+ worktree: string
12
+ /** A folder inside the main checkout. */
13
+ subfolder: string
14
+ /** A symlink to the main checkout. */
15
+ link: string
16
+ /** A folder in no git repo. */
17
+ outside: string
18
+ }
19
+
20
+ /**
21
+ * Real throwaway checkouts under `dir`: a git repo with one commit, a
22
+ * worktree of it, a subfolder, a symlink to it, and a folder outside git.
23
+ * For the `/luca-unstick` and `/luca-retro` helpers' repo lookup, which must
24
+ * find the same runs from each of them.
25
+ *
26
+ * @example
27
+ * const { main, worktree } = await makeCheckouts({ dir })
28
+ */
29
+ export const makeCheckouts = async ({
30
+ dir,
31
+ }: {
32
+ dir: string
33
+ }): Promise<Checkouts> => {
34
+ const main = join(dir, 'app')
35
+ const worktree = join(dir, 'app-worktree')
36
+ const subfolder = join(main, 'src', 'deep')
37
+ const link = join(dir, 'app-link')
38
+ const outside = join(dir, 'not-git')
39
+ await mkdir(subfolder, { recursive: true })
40
+ await mkdir(outside, { recursive: true })
41
+ await Bun.write(join(subfolder, 'a.txt'), 'a\n')
42
+ await $`git init -q -b main && git add -A && git -c user.name=t -c user.email=t@t -c commit.gpgsign=false commit -qm init`
43
+ .cwd(main)
44
+ .quiet()
45
+ await $`git worktree add -q -b side ${worktree}`.cwd(main).quiet()
46
+ await symlink(main, link)
47
+ return { main, worktree, subfolder, link, outside }
48
+ }
@@ -4,6 +4,7 @@ import {
4
4
  type Finding,
5
5
  type FindingRuling,
6
6
  type LensName,
7
+ type MergeDanger,
7
8
  } from '../agents/role-results'
8
9
  import type { JournalEntry, StuckReason } from '../journal/journal-record'
9
10
 
@@ -22,6 +23,8 @@ export const finalReviewStarted = ({
22
23
  lenses,
23
24
  files,
24
25
  rules,
26
+ slow_due,
27
+ fresh,
25
28
  }: {
26
29
  round: number
27
30
  /** Defaults to `b0`, where the fixtures' run branch starts. */
@@ -33,6 +36,16 @@ export const finalReviewStarted = ({
33
36
  /** Defaults to `src/sum.ts` and its test. */
34
37
  files?: string[]
35
38
  rules?: { path: string; text: string | null }[]
39
+ /**
40
+ * Round 1's slow tests are due (#563). Left out, the record has no
41
+ * `slow_due`, as an older engine's has none.
42
+ */
43
+ slow_due?: boolean
44
+ /**
45
+ * The final review's fresh pass (#548). Left out, the record has no
46
+ * `fresh`, as an older engine's has none.
47
+ */
48
+ fresh?: boolean
36
49
  }): JournalEntry => ({
37
50
  kind: 'final_review_started',
38
51
  ticket: null,
@@ -44,6 +57,8 @@ export const finalReviewStarted = ({
44
57
  lenses: lenses ?? [...LENS_NAMES],
45
58
  files: files ?? ['src/sum.test.ts', 'src/sum.ts'],
46
59
  rules: rules ?? [],
60
+ ...(slow_due === undefined ? {} : { slow_due }),
61
+ ...(fresh === undefined ? {} : { fresh }),
47
62
  },
48
63
  })
49
64
 
@@ -79,12 +94,18 @@ export const lensReviewed = ({
79
94
  findings,
80
95
  rulings,
81
96
  assumptions,
97
+ summary_picture,
98
+ merge_danger,
82
99
  }: {
83
100
  lens: LensName
84
101
  round: number
85
102
  findings?: Finding[]
86
103
  rulings?: FindingRuling[]
87
104
  assumptions?: string[]
105
+ /** The integration lens's Summary picture (#513); left out by default. */
106
+ summary_picture?: string | null
107
+ /** The integration lens's Merge Danger (#513); left out by default. */
108
+ merge_danger?: MergeDanger | null
88
109
  }): JournalEntry[] => {
89
110
  const list = findings ?? []
90
111
  const role = lensRole({ lens })
@@ -118,6 +139,10 @@ export const lensReviewed = ({
118
139
  rulings: rulings ?? [],
119
140
  summary: `The ${lens} lens looked.`,
120
141
  assumptions: assumptions ?? [],
142
+ ...(summary_picture === undefined
143
+ ? {}
144
+ : { summary_picture }),
145
+ ...(merge_danger === undefined ? {} : { merge_danger }),
121
146
  },
122
147
  },
123
148
  },
@@ -148,6 +173,8 @@ export const lensRound = ({
148
173
  findings,
149
174
  from_sha,
150
175
  head_sha,
176
+ slow_due,
177
+ fresh,
151
178
  }: {
152
179
  round: number
153
180
  /** Defaults to every lens. */
@@ -155,16 +182,50 @@ export const lensRound = ({
155
182
  findings?: Partial<Record<LensName, Finding[]>>
156
183
  from_sha?: string
157
184
  head_sha?: string
185
+ /** The round's slow tests are due (#563); see `finalReviewStarted`. */
186
+ slow_due?: boolean
187
+ /** The fresh pass (#548); see `finalReviewStarted`. */
188
+ fresh?: boolean
158
189
  }): JournalEntry[] => {
159
190
  const due = lenses ?? [...LENS_NAMES]
160
191
  return [
161
- finalReviewStarted({ round, lenses: due, from_sha, head_sha }),
192
+ finalReviewStarted({
193
+ round,
194
+ lenses: due,
195
+ from_sha,
196
+ head_sha,
197
+ slow_due,
198
+ fresh,
199
+ }),
162
200
  ...due.flatMap((lens) =>
163
201
  lensReviewed({ lens, round, findings: findings?.[lens] })
164
202
  ),
165
203
  ]
166
204
  }
167
205
 
206
+ /**
207
+ * The final review's slow tests on the run branch (#563): `bun test
208
+ * .e2e.test`, passing or failing.
209
+ */
210
+ export const slowTestsRun = ({ ok }: { ok: boolean }): JournalEntry => ({
211
+ kind: 'gates_run',
212
+ ticket: null,
213
+ role: null,
214
+ content: {
215
+ target: 'final_slow',
216
+ ok,
217
+ checks: [
218
+ {
219
+ name: 'slow_test',
220
+ command: 'bun test .e2e.test',
221
+ ok,
222
+ exit_code: ok ? 0 : 1,
223
+ output: ok ? '' : '1 fail',
224
+ },
225
+ ],
226
+ },
227
+ })
228
+
168
229
  /** A final review fix round starts. */
169
230
  export const finalReviewFixing = ({
170
231
  round,
@@ -0,0 +1,143 @@
1
+ import {
2
+ commitMade,
3
+ gatesRun,
4
+ implemented,
5
+ intakePassed,
6
+ leftoverScan,
7
+ practiceTicket,
8
+ pushed,
9
+ runBranchCreated,
10
+ testsWritten,
11
+ ticketBuilt,
12
+ withInstalls,
13
+ } from './build-fixtures'
14
+ import {
15
+ finalReviewFixing,
16
+ lensFinding,
17
+ lensRound,
18
+ } from './final-review-fixtures'
19
+ import { recordsFrom } from './intake-fixtures'
20
+
21
+ import { decideSteps, type EngineAction } from '../core/decide'
22
+ import type { JournalEntry } from '../journal/journal-record'
23
+
24
+ /**
25
+ * The final review's decision-step tests' shared journal: practice ticket
26
+ * #11 built and pushed, its lens findings, and its fix rounds, with the
27
+ * steps the decision step takes after them.
28
+ */
29
+
30
+ export const TICKET = practiceTicket({ number: 11 })
31
+
32
+ /** Every step the decision step can take now, after these entries. */
33
+ export const stepsAfter = (
34
+ entries: JournalEntry[],
35
+ tickets = [TICKET]
36
+ ): EngineAction[] =>
37
+ decideSteps({
38
+ records: recordsFrom({
39
+ entries: [
40
+ ...intakePassed({ tickets }),
41
+ ...withInstalls({ entries }),
42
+ ],
43
+ }),
44
+ })
45
+
46
+ /** The one step the decision step takes after these entries. */
47
+ export const stepAfter = (entries: JournalEntry[]): EngineAction => {
48
+ const steps = stepsAfter(entries)
49
+ if (steps.length !== 1) {
50
+ throw new Error(steps.map(({ type }) => type).join(', '))
51
+ }
52
+ return steps[0] as EngineAction
53
+ }
54
+
55
+ /** The run branch with ticket #11 built, joined, and pushed. */
56
+ export const built = (): JournalEntry[] => [
57
+ runBranchCreated(),
58
+ ...ticketBuilt({ ticket: 11 }),
59
+ ]
60
+
61
+ /** The prompt of the `launch_lens` step for this lens. */
62
+ export const lensPrompt = (steps: EngineAction[], lens: string): string => {
63
+ const step = steps.find(
64
+ (entry) => entry.type === 'launch_lens' && entry.lens === lens
65
+ )
66
+ if (step?.type !== 'launch_lens') throw new Error(`no ${lens} lens`)
67
+ return step.prompt
68
+ }
69
+
70
+ export const SECURITY = lensFinding({ id: 'S1', title: 'Sum trusts its input' })
71
+ export const ARCH_TEST = lensFinding({
72
+ id: 'A1',
73
+ kind: 'test',
74
+ severity: 'blocker',
75
+ title: 'The tests reach into private helpers',
76
+ file: 'src/sum.test.ts',
77
+ })
78
+ export const NIT = lensFinding({
79
+ id: 'N1',
80
+ severity: 'nit',
81
+ title: 'Shorter name',
82
+ })
83
+
84
+ /** Round 1 with a code finding (security), a test finding (architecture), and a nit. */
85
+ export const findingsRound = (): JournalEntry[] =>
86
+ lensRound({
87
+ round: 1,
88
+ findings: {
89
+ security: [SECURITY],
90
+ architecture: [ARCH_TEST],
91
+ simplification: [NIT],
92
+ },
93
+ })
94
+
95
+ /** Fix round 1 answered: the test-writer, then a fresh implementer. */
96
+ export const fixersAnswered = ({
97
+ security,
98
+ }: {
99
+ security: 'fixed' | 'wont_fix'
100
+ }): JournalEntry[] => [
101
+ finalReviewFixing({ round: 1 }),
102
+ testsWritten({
103
+ ticket: null,
104
+ finding_responses: [
105
+ { finding_id: 'architecture-A1', response: 'fixed', reason: '' },
106
+ ],
107
+ }),
108
+ implemented({
109
+ ticket: null,
110
+ finding_responses: [
111
+ {
112
+ finding_id: 'security-S1',
113
+ response: security,
114
+ reason: security === 'fixed' ? '' : 'Inputs are typed numbers.',
115
+ },
116
+ ],
117
+ }),
118
+ ]
119
+
120
+ /** The fixes pass the gates, are committed as `sha`, and pushed. */
121
+ export const fixesLanded = ({ sha }: { sha: string }): JournalEntry[] => [
122
+ gatesRun({ ticket: null, target: 'run_branch', ok: true }),
123
+ leftoverScan({ ticket: null, stage: 'fix' }),
124
+ commitMade({ ticket: null, stage: 'fix', sha, files: ['src/sum.ts'] }),
125
+ pushed({ ticket: null, sha }),
126
+ ]
127
+
128
+ /** A whole fix round of the security finding, up to its push. */
129
+ export const securityFixRound = ({
130
+ round,
131
+ }: {
132
+ round: number
133
+ }): JournalEntry[] => [
134
+ finalReviewFixing({ round }),
135
+ implemented({
136
+ ticket: null,
137
+ session_id: `impl-${round}`,
138
+ finding_responses: [
139
+ { finding_id: 'security-S1', response: 'fixed', reason: '' },
140
+ ],
141
+ }),
142
+ ...fixesLanded({ sha: `fix-${round}` }),
143
+ ]
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Journal entry builders for plan limits: a `rate_limit_event`'s reading as
3
+ * the SDK sends it, a limit wait started and ended, and a billing stop.
4
+ */
5
+ import type { JournalEntry } from '../journal/journal-record'
6
+
7
+ /** Seconds since the epoch for an ISO time, as `resetsAt` gives it. */
8
+ export const epochSeconds = (iso: string): number =>
9
+ Math.floor(Date.parse(iso) / 1000)
10
+
11
+ /**
12
+ * One `rate_limit_event`'s info as the SDK sends it, with the windows'
13
+ * utilization under `unifiedWindows` as real readings have it.
14
+ */
15
+ export const rateLimitReading = ({
16
+ status,
17
+ rate_limit_type,
18
+ resets_at,
19
+ is_using_overage,
20
+ windows,
21
+ }: {
22
+ status: 'allowed' | 'allowed_warning' | 'rejected'
23
+ /** Defaults to `five_hour`. */
24
+ rate_limit_type?: string
25
+ /** An ISO time; leave it out for a reading with no `resetsAt`. */
26
+ resets_at?: string
27
+ is_using_overage?: boolean
28
+ /** Utilization (0 to 1) per window, such as `{ five_hour: 0.2 }`. */
29
+ windows?: Record<string, number>
30
+ }): Record<string, unknown> => ({
31
+ status,
32
+ rateLimitType: rate_limit_type ?? 'five_hour',
33
+ ...(resets_at === undefined ? {} : { resetsAt: epochSeconds(resets_at) }),
34
+ overageStatus: 'rejected',
35
+ overageDisabledReason: 'org_level_disabled',
36
+ isUsingOverage: is_using_overage ?? false,
37
+ unifiedWindows: Object.fromEntries(
38
+ Object.entries(windows ?? {}).map(([name, utilization]) => [
39
+ name,
40
+ { utilization },
41
+ ])
42
+ ),
43
+ })
44
+
45
+ /** A limit wait started, hit by #11's test-writer unless told otherwise. */
46
+ export const limitWaitStarted = ({
47
+ until,
48
+ resets_at,
49
+ rate_limit_type,
50
+ }: {
51
+ until: string
52
+ resets_at: string | null
53
+ /** Defaults to `five_hour`. */
54
+ rate_limit_type?: string
55
+ }): JournalEntry => ({
56
+ kind: 'limit_wait_started',
57
+ ticket: null,
58
+ role: null,
59
+ content: {
60
+ until,
61
+ resets_at,
62
+ rate_limit_type: rate_limit_type ?? 'five_hour',
63
+ hit_ticket: 11,
64
+ hit_role: 'test-writer',
65
+ },
66
+ })
67
+
68
+ export const limitWaitEnded = ({ until }: { until: string }): JournalEntry => ({
69
+ kind: 'limit_wait_ended',
70
+ ticket: null,
71
+ role: null,
72
+ content: { until },
73
+ })
74
+
75
+ /** The run stopped for good on a sign of per-token billing. */
76
+ export const billingStopped = ({
77
+ reason,
78
+ }: {
79
+ reason: string
80
+ }): JournalEntry => ({
81
+ kind: 'run_stopped',
82
+ ticket: null,
83
+ role: null,
84
+ content: { reason, role: null, billing: true },
85
+ })
@@ -0,0 +1,57 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+
4
+ import { LiveFileSchema, type LiveFile } from '../core/live-file'
5
+
6
+ /**
7
+ * The run's live file (#628) as the engine's tests read it: `live.json`
8
+ * parsed with the writer's own schema (`core/live-file.ts`), never cast, so
9
+ * a field the writer renames or drops fails the test that reads it.
10
+ * The board reads the same file with its own, more forgiving schemas
11
+ * (`packages/board/server/live-file.ts`).
12
+ */
13
+
14
+ export {
15
+ LiveFileSchema,
16
+ type LiveAgent,
17
+ type LiveFile,
18
+ type LiveHelper,
19
+ type LiveWait,
20
+ } from '../core/live-file'
21
+
22
+ /** The live file of the practice run (`run-1`) in `dir`. */
23
+ export const liveFileIn = (dir: string): string =>
24
+ join(dir, 'runs', 'run-1', 'live.json')
25
+
26
+ /**
27
+ * The live file at `file`, or `null` while there is none. A file that is
28
+ * there but doesn't fit `LiveFileSchema` throws, naming the field.
29
+ */
30
+ export const readLive = (file: string): LiveFile | null => {
31
+ let json: unknown
32
+ try {
33
+ json = JSON.parse(readFileSync(file, 'utf8'))
34
+ } catch {
35
+ return null
36
+ }
37
+ return LiveFileSchema.parse(json)
38
+ }
39
+
40
+ /** The live file once `until` holds for it, or `null` after `ms`. */
41
+ export const waitForLive = async ({
42
+ file,
43
+ until,
44
+ ms = 3000,
45
+ }: {
46
+ file: string
47
+ until: (live: LiveFile) => boolean
48
+ ms?: number
49
+ }): Promise<LiveFile | null> => {
50
+ const deadline = Date.now() + ms
51
+ while (Date.now() < deadline) {
52
+ const live = readLive(file)
53
+ if (live !== null && until(live)) return live
54
+ await Bun.sleep(20)
55
+ }
56
+ return null
57
+ }
@@ -189,14 +189,16 @@ export const HAPPY_TURNS: ScriptedTurn[] = [
189
189
  ]
190
190
 
191
191
  /**
192
- * The turns, then a clean final review of spec #10 and an empty learner to
193
- * fall back on: the scripted launcher plays the first unused turn of a
194
- * role, so lens and learner turns in `turns` go first, a lens the turns
195
- * don't script approves, and a learner they don't script learns nothing.
192
+ * The turns, then two clean final review rounds of spec #10 (round 1's, and
193
+ * the fresh pass's after a fix round, #548) and an empty learner to fall
194
+ * back on: the scripted launcher plays the first unused turn of a role, so
195
+ * lens and learner turns in `turns` go first, a lens the turns don't script
196
+ * approves, and a learner they don't script learns nothing.
196
197
  */
197
198
  const withCleanLenses = (turns: ScriptedTurn[]): ScriptedTurn[] => [
198
199
  ...turns,
199
200
  ...CLEAN_LENS_TURNS(10),
201
+ ...CLEAN_LENS_TURNS(10),
200
202
  EMPTY_LEARNER_TURN(10),
201
203
  ]
202
204
 
@@ -347,6 +349,7 @@ export const createPracticeRepo = async ({
347
349
  memory,
348
350
  git: wrapGit,
349
351
  journal: wrapJournal,
352
+ live_file,
350
353
  }: {
351
354
  /**
352
355
  * Scripted agents' turns, then a clean final review for any lens they
@@ -385,6 +388,8 @@ export const createPracticeRepo = async ({
385
388
  * Defaults to the journal as is.
386
389
  */
387
390
  journal?: (real: Journal) => Journal
391
+ /** Where the engine keeps the run's live file (#628). Left out, it keeps none. */
392
+ live_file?: string
388
393
  }) => {
389
394
  const loaded = await loadEngineConfig({ repo_root: repo })
390
395
  if (!loaded.ok) throw new Error(loaded.error)
@@ -419,6 +424,7 @@ export const createPracticeRepo = async ({
419
424
  memory === undefined
420
425
  ? undefined
421
426
  : { client: memory.client, timeout_ms: memory.timeout_ms },
427
+ live_file,
422
428
  })
423
429
  return {
424
430
  action,
@@ -20,7 +20,7 @@ import {
20
20
  * with a second ticket on top, so the board shows a blocked ticket waiting
21
21
  * and then building. No GitHub, no models, no setup.
22
22
  *
23
- * Seam 2 (`core/run-one-ticket.test.ts`) runs on `practice-repo.ts` itself.
23
+ * Seam 2 (`core/run-one-ticket.e2e.test.ts`) runs on `practice-repo.ts` itself.
24
24
  */
25
25
 
26
26
  export { makePracticeRepo, PRACTICE_SPEC_NUMBER }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Journal entry builders for a stuck ticket and the owner's reply: the stuck
3
+ * report, a comment read, a reply word taken, and a ticket retried or skipped.
4
+ */
5
+ import { SPEC_OWNER } from './intake-fixtures'
6
+
7
+ import type { JournalEntry } from '../journal/journal-record'
8
+
9
+ /** The engine told the spec issue that a ticket is stuck, in comment `comment_id`. */
10
+ export const stuckReported = ({
11
+ ticket,
12
+ comment_id,
13
+ }: {
14
+ ticket: number
15
+ /** Defaults to 100 + the ticket number. */
16
+ comment_id?: number
17
+ }): JournalEntry => ({
18
+ kind: 'stuck_reported',
19
+ ticket,
20
+ role: null,
21
+ content: { comment_id: comment_id ?? 100 + ticket, body: 'stuck' },
22
+ })
23
+
24
+ /** A comment the engine read on the spec issue. */
25
+ export const commentRead = ({
26
+ comment_id,
27
+ body,
28
+ author,
29
+ }: {
30
+ comment_id: number
31
+ body: string
32
+ /** Defaults to the spec's owner. */
33
+ author?: string
34
+ }): JournalEntry => ({
35
+ kind: 'comment_read',
36
+ ticket: null,
37
+ role: null,
38
+ content: { comment_id, author: author ?? SPEC_OWNER, body },
39
+ })
40
+
41
+ /** The engine took a reply word from the spec's owner. */
42
+ export const replyReceived = ({
43
+ word,
44
+ ticket,
45
+ comment_id,
46
+ }: {
47
+ word: 'retry' | 'skip' | 'stop' | 'ship'
48
+ ticket: number | null
49
+ comment_id: number
50
+ }): JournalEntry => ({
51
+ kind: 'reply_received',
52
+ ticket,
53
+ role: null,
54
+ content: { word, ticket, comment_id, author: SPEC_OWNER },
55
+ })
56
+
57
+ /** A retried ticket: resumed, started over, or refused. */
58
+ export const ticketRetried = ({
59
+ ticket,
60
+ mode,
61
+ }: {
62
+ ticket: number
63
+ mode: 'resume' | 'restart' | 'refused'
64
+ }): JournalEntry => ({
65
+ kind: 'ticket_retried',
66
+ ticket,
67
+ role: null,
68
+ content: {
69
+ mode,
70
+ base_sha: mode === 'restart' ? 'tip-sha' : null,
71
+ problems: mode === 'refused' ? ['no criteria'] : [],
72
+ answer_id: null,
73
+ },
74
+ })
75
+
76
+ /** A ticket left out of the run. */
77
+ export const ticketSkipped = ({
78
+ ticket,
79
+ because,
80
+ }: {
81
+ ticket: number
82
+ because?: number
83
+ }): JournalEntry => ({
84
+ kind: 'ticket_skipped',
85
+ ticket,
86
+ role: null,
87
+ content: { because: because ?? null },
88
+ })