@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,148 @@
1
+ /**
2
+ * A run's first records: `run_started`, and on a resume `engine_resumed`
3
+ * and what came of taking the repo's config again.
4
+ */
5
+ import values from 'lodash/values'
6
+
7
+ import { configChanges } from '../config/config-reload'
8
+ import type {
9
+ EngineConfig,
10
+ LoadEngineConfigResult,
11
+ } from '../config/engine-config'
12
+ import { lucaVersion } from '../config/luca-version'
13
+ import { configProblems } from '../intake/intake-checks'
14
+ import type { Journal } from '../journal/journal'
15
+ import type { JournalRecord } from '../journal/journal-record'
16
+ import { replayRun } from '../journal/replay'
17
+
18
+ /**
19
+ * Starts a run by writing its first record. The spec and config it names are
20
+ * what every later step of the run works from; the Luca version it names is
21
+ * the one the run started on. A run started `alongside` journals right away
22
+ * that it skips the repo wait (`repo_wait_skipped`, #615).
23
+ */
24
+ export const startRun = ({
25
+ journal,
26
+ spec_number,
27
+ config,
28
+ base_branch,
29
+ memory,
30
+ repo,
31
+ alongside,
32
+ luca_version = lucaVersion(),
33
+ }: {
34
+ journal: Journal
35
+ spec_number: number
36
+ config: EngineConfig
37
+ /** The branch the run branch starts from. Defaults to `main`. */
38
+ base_branch?: string
39
+ /**
40
+ * Turns memory on (#370), with the project's vault (`null` searches only
41
+ * `default`). Leave it out for a run without memory.
42
+ */
43
+ memory?: { project_vault: string | null }
44
+ /** The repo the run is on, so a resume can find it. Defaults to `null`. */
45
+ repo?: string | null
46
+ /** Never wait for another run on the repo (`luca-run --alongside`). */
47
+ alongside?: boolean
48
+ /** Luca's version. Defaults to the engine's own. */
49
+ luca_version?: string
50
+ }): JournalRecord => {
51
+ const started = journal.append({
52
+ kind: 'run_started',
53
+ ticket: null,
54
+ role: null,
55
+ content: {
56
+ spec_number,
57
+ config,
58
+ base_branch,
59
+ memory: memory ?? null,
60
+ repo,
61
+ luca_version,
62
+ },
63
+ })
64
+ if (alongside === true) {
65
+ journal.append({
66
+ kind: 'repo_wait_skipped',
67
+ ticket: null,
68
+ role: null,
69
+ content: {},
70
+ })
71
+ }
72
+ return started
73
+ }
74
+
75
+ /**
76
+ * Records that the engine started again on a run's journal, on Luca's
77
+ * version `luca_version`, before its first step.
78
+ */
79
+ export const recordResume = ({
80
+ journal,
81
+ luca_version = lucaVersion(),
82
+ }: {
83
+ journal: Journal
84
+ /** Luca's version. Defaults to the engine's own. */
85
+ luca_version?: string
86
+ }): JournalRecord =>
87
+ journal.append({
88
+ kind: 'engine_resumed',
89
+ ticket: null,
90
+ role: null,
91
+ content: { luca_version },
92
+ })
93
+
94
+ /**
95
+ * On a resume, takes the repo's config again (#516), just read as
96
+ * `loaded`, and journals what came of it, right after `recordResume`:
97
+ *
98
+ * - a `config_reloaded` with the build fields (`RELOADED_FIELDS`) that
99
+ * changed, old to new, which replay puts on the run's config;
100
+ * - nothing, when none changed (frozen fields never count, see
101
+ * `FROZEN_FIELDS`);
102
+ * - a `config_reload_refused` with the reason, when the file is missing,
103
+ * not JSON, not a valid config, or one intake would refuse
104
+ * (`configProblems`, on the run's tickets). The run keeps its config.
105
+ *
106
+ * Never throws for a bad file: the run always goes on. Returns the record
107
+ * it appended, or `null`. A journal with no config yet (not a run) gets
108
+ * nothing; the decision step says what is wrong with it.
109
+ *
110
+ * @example
111
+ * recordResume({ journal })
112
+ * const reload = recordConfigReload({ journal, loaded: await loadEngineConfig({ repo_root }) })
113
+ */
114
+ export const recordConfigReload = ({
115
+ journal,
116
+ loaded,
117
+ }: {
118
+ journal: Journal
119
+ loaded: LoadEngineConfigResult
120
+ }): JournalRecord | null => {
121
+ const state = replayRun({ records: journal.read() })
122
+ if (state.config === null) return null
123
+ const refuse = (reason: string): JournalRecord =>
124
+ journal.append({
125
+ kind: 'config_reload_refused',
126
+ ticket: null,
127
+ role: null,
128
+ content: { reason },
129
+ })
130
+ if (!loaded.ok) return refuse(loaded.error)
131
+ const problems = configProblems({
132
+ config: loaded.config,
133
+ tickets: values(state.snapshot?.tickets ?? {}),
134
+ })
135
+ if (problems.length > 0) return refuse(problems.join('\n'))
136
+ const changes = configChanges({
137
+ current: state.config,
138
+ next: loaded.config,
139
+ })
140
+ return changes.length === 0
141
+ ? null
142
+ : journal.append({
143
+ kind: 'config_reloaded',
144
+ ticket: null,
145
+ role: null,
146
+ content: { changes },
147
+ })
148
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The comments on the spec issue when the whole run is stuck: on its run
3
+ * budget, or on a pull request that didn't open.
4
+ */
5
+
6
+ /** A token count with thousands separators, such as `50,000`. */
7
+ const tokensText = (tokens: number): string =>
8
+ String(tokens).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
9
+
10
+ /**
11
+ * Why the run is stuck on its budget, in one line.
12
+ *
13
+ * @example
14
+ * runBudgetDetail({ tokens: 50_100, budget: 50_000 })
15
+ * // 'The run used 50,100 tokens of its run budget of 50,000.'
16
+ */
17
+ export const runBudgetDetail = ({
18
+ tokens,
19
+ budget,
20
+ }: {
21
+ tokens: number
22
+ budget: number
23
+ }): string =>
24
+ `The run used ${tokensText(tokens)} tokens of its run budget of ${tokensText(budget)}.`
25
+
26
+ /**
27
+ * The comment on the spec issue when the whole run is stuck on its run
28
+ * budget: why, how big one budget is, and the replies `retry` and `stop`
29
+ * (never `skip` or `ship`).
30
+ *
31
+ * @example
32
+ * runStuckComment({ detail, budget: 9_000_000 })
33
+ * // '**The run is stuck: it used up its run budget**\n\nWhy: ...'
34
+ */
35
+ export const runStuckComment = ({
36
+ detail,
37
+ budget,
38
+ }: {
39
+ detail: string
40
+ /** One full run budget. */
41
+ budget: number
42
+ }): string =>
43
+ [
44
+ '**The run is stuck: it used up its run budget**',
45
+ [
46
+ `Why: ${detail}`,
47
+ `One run budget is ${tokensText(budget)} tokens (input, output, and cache writes; cache reads don't count). A repo changes it with \`run_budget_tokens\` in \`.luca/config.json\`.`,
48
+ 'Nothing new starts until you reply. Other stuck work waits for its reply until the run carries on.',
49
+ ].join('\n'),
50
+ [
51
+ 'Reply with one word on this issue:',
52
+ `- \`retry\`: add half a run budget (${tokensText(Math.floor(budget / 2))} tokens) and carry on where it stopped. \`retry +500k\` or \`retry +3M\` adds the amount you name instead.`,
53
+ '- `stop`: end the run without a PR. The branch is kept.',
54
+ '',
55
+ 'Only the spec owner counts.',
56
+ ].join('\n'),
57
+ ].join('\n\n')
58
+
59
+ /**
60
+ * The comment on the spec issue when the run is stuck because its pull
61
+ * request didn't open (#508): `gh`'s error, where the PR's full text is
62
+ * saved, and the replies `retry` and `stop`.
63
+ *
64
+ * @example
65
+ * pullRequestStuckComment({ detail: 'gh pr create failed (exit code 1): ...' })
66
+ * // "**The run is stuck: its pull request didn't open**\n\nWhy: ..."
67
+ */
68
+ export const pullRequestStuckComment = ({
69
+ detail,
70
+ }: {
71
+ detail: string
72
+ }): string =>
73
+ [
74
+ "**The run is stuck: its pull request didn't open**",
75
+ [
76
+ `Why: ${detail}`,
77
+ `The run branch is pushed, and the PR's full text is saved in the run folder as \`pull-request.md\`.`,
78
+ ].join('\n'),
79
+ [
80
+ 'Reply with one word on this issue:',
81
+ '- `retry`: try to open the PR again. A PR already open from the run branch, such as one you opened by hand, is reused, never opened twice.',
82
+ '- `stop`: end the run without a PR. The branch is kept.',
83
+ '',
84
+ 'Only the spec owner counts.',
85
+ ].join('\n'),
86
+ ].join('\n\n')
@@ -0,0 +1,193 @@
1
+ /**
2
+ * What the scheduler knows of an action: its key, its step's name, its
3
+ * ticket and role, and which actions wait, run slow, or use the run branch.
4
+ */
5
+ import has from 'lodash/has'
6
+ import partition from 'lodash/partition'
7
+
8
+ import type { EngineAction } from './decide'
9
+ import { isFinalReviewAction } from './decide-final-review'
10
+
11
+ import type { JournalRecord } from '../journal/journal-record'
12
+ import { crashesAfter, type CrashCounts } from '../journal/step-records'
13
+
14
+ /**
15
+ * The ticket an action works on, or `null` for a run-level action. The
16
+ * plan's actions are the whole run's, whichever ticket hit the limit; a
17
+ * ticket's usage record is that ticket's. Reading and taking replies is
18
+ * the run's too (see `keyOf`).
19
+ */
20
+ export const ticketOf = (action: EngineAction): number | null => {
21
+ switch (action.type) {
22
+ case 'start_limit_wait':
23
+ case 'wait_for_limit':
24
+ case 'stop_for_billing':
25
+ case 'wait_for_reply':
26
+ case 'take_reply':
27
+ case 'ignore_reply':
28
+ return null
29
+ case 'record_usage':
30
+ return action.usage.ticket
31
+ default:
32
+ return 'ticket' in action && typeof action.ticket === 'number'
33
+ ? action.ticket
34
+ : null
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Replies are read and taken beside the other steps, one at a time, under
40
+ * their own key: waiting for a reply never holds a ticket up.
41
+ */
42
+ const REPLY_ACTIONS: ReadonlySet<EngineAction['type']> = new Set([
43
+ 'wait_for_reply',
44
+ 'take_reply',
45
+ 'ignore_reply',
46
+ ])
47
+
48
+ /**
49
+ * The scheduler's key for an action: at most one action per key runs at a
50
+ * time. Reading and taking replies has its own (`replies`). Each lens has
51
+ * its own (`lens:<lens>`), so the five lenses review at once; the final review's other steps share `final`; a ticket's steps its
52
+ * number; and the run's own steps `run`, which only ever run alone.
53
+ */
54
+ export const keyOf = (action: EngineAction): string => {
55
+ if (REPLY_ACTIONS.has(action.type)) return 'replies'
56
+ // A search waits under the key of the step it comes before: its
57
+ // ticket's, the final review's, or (the run's start) the run's.
58
+ if (action.type === 'recall_memories') {
59
+ if (action.ticket !== null) return String(action.ticket)
60
+ return action.point === 'run_start' ? 'run' : 'final'
61
+ }
62
+ if (action.type === 'launch_lens') return `lens:${action.lens}`
63
+ if (isFinalReviewAction(action)) return 'final'
64
+ const ticket = ticketOf(action)
65
+ return ticket === null ? 'run' : String(ticket)
66
+ }
67
+
68
+ /** Actions that are an agent's turn: their step names the role. */
69
+ const AGENT_ACTIONS: ReadonlySet<EngineAction['type']> = new Set([
70
+ 'launch_agent',
71
+ 'follow_up_agent',
72
+ 'launch_lens',
73
+ 'launch_final_fixer',
74
+ 'follow_up_final_fixer',
75
+ ])
76
+
77
+ /** The agent role of an agent's turn (the learner's is `learner`), else `null`. */
78
+ export const agentRoleOf = (action: EngineAction): string | null => {
79
+ if (action.type === 'launch_learner') return 'learner'
80
+ return AGENT_ACTIONS.has(action.type) &&
81
+ 'role' in action &&
82
+ action.role !== null
83
+ ? action.role
84
+ : null
85
+ }
86
+
87
+ /**
88
+ * A step's name in the journal: its action type, plus `:<role>` for an
89
+ * agent's turn, such as `follow_up_agent:implementer`.
90
+ *
91
+ * @example
92
+ * stepOf({ type: 'run_gates', ticket: 11, target: 'ticket' }) // 'run_gates'
93
+ */
94
+ export const stepOf = (action: EngineAction): string => {
95
+ const role = agentRoleOf(action)
96
+ return role === null ? action.type : `${action.type}:${role}`
97
+ }
98
+
99
+ /**
100
+ * Waits get no step records: a crash during one is no step's fault, and a
101
+ * restarted engine just waits again (a limit wait until the same time).
102
+ */
103
+ export const WAIT_ACTIONS: ReadonlySet<EngineAction['type']> = new Set([
104
+ 'wait_for_reply',
105
+ 'wait_for_limit',
106
+ 'wait_for_usage_line',
107
+ 'wait_for_repo',
108
+ ])
109
+
110
+ /**
111
+ * Steps that can run for minutes: installs, test runs, and agents' turns.
112
+ * The board hears of them as they start, so a run never looks frozen.
113
+ */
114
+ export const SLOW_ACTIONS: ReadonlySet<EngineAction['type']> = new Set([
115
+ ...AGENT_ACTIONS,
116
+ 'launch_learner',
117
+ 'install_dependencies',
118
+ 'run_baseline_tests',
119
+ 'run_red_check',
120
+ 'check_already_done',
121
+ 'run_gates',
122
+ 'run_final_gates',
123
+ 'run_final_slow_tests',
124
+ ])
125
+
126
+ /**
127
+ * Actions that read or move the run branch. At most one runs at a time, so
128
+ * joins, their gates, pushes, and new worktrees see one run branch. The
129
+ * final review's fixers, gates, slow tests (#563), commit, and push work in
130
+ * the run branch's worktree; its lenses only read it. Nothing writes there
131
+ * while a lens runs, though: its after-turn guard judges that worktree, so
132
+ * the slow tests wait for round 1's lenses (see `decideFinalReview`).
133
+ */
134
+ export const usesRunBranch = (action: EngineAction): boolean => {
135
+ switch (action.type) {
136
+ case 'create_ticket_worktree':
137
+ case 'join_run_branch':
138
+ case 'push_run_branch':
139
+ case 'rebase_ticket':
140
+ case 'start_final_review':
141
+ case 'launch_final_fixer':
142
+ case 'follow_up_final_fixer':
143
+ case 'run_final_gates':
144
+ case 'run_final_slow_tests':
145
+ case 'commit_final_fix':
146
+ case 'push_final_fixes':
147
+ case 'write_changeset':
148
+ case 'ship_final_review':
149
+ case 'undo_join':
150
+ case 'retry_ticket':
151
+ return true
152
+ case 'run_gates':
153
+ case 'install_dependencies':
154
+ return action.target === 'run_branch'
155
+ // It puts back a run branch a join crashes cut off too often left.
156
+ case 'mark_stuck':
157
+ return action.reason === 'crashed'
158
+ default:
159
+ return false
160
+ }
161
+ }
162
+
163
+ /** The crash counts per key, from the journal's records. */
164
+ export const crashesIn = (records: JournalRecord[]): CrashCounts =>
165
+ records.reduce<CrashCounts>(
166
+ (crashes, record) => crashesAfter({ crashes, record }),
167
+ {}
168
+ )
169
+
170
+ /**
171
+ * The actions on keys a crash cut a step off first, in their order, then
172
+ * the rest: a step a crash cut off (or the stop for it) starts before any
173
+ * other, so a half-done change to the run branch, such as a join, is put
174
+ * right before another step reads the run branch.
175
+ */
176
+ export const redosFirst = ({
177
+ actions,
178
+ crashes,
179
+ }: {
180
+ actions: EngineAction[]
181
+ crashes: CrashCounts
182
+ }): EngineAction[] => {
183
+ const [redos, rest] = partition(actions, (action) =>
184
+ has(crashes, keyOf(action))
185
+ )
186
+ return [...redos, ...rest]
187
+ }
188
+
189
+ /** The session a follow-up goes to, if the action is one. */
190
+ export const followUpSessionOf = (action: EngineAction): string[] =>
191
+ action.type === 'follow_up_agent' || action.type === 'follow_up_final_fixer'
192
+ ? [action.session_id]
193
+ : []
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The stuck details for a ticket that needs a change only the owner may make, or
3
+ * whose test was sent back as bad.
4
+ */
5
+ import upperFirst from 'lodash/upperFirst'
6
+
7
+ import type { AgentRole, BadTest, BadTestSource } from '../agents/role-results'
8
+ import { clipOutput } from '../shell/run-command'
9
+
10
+ /**
11
+ * The detail of a ticket or the final review stuck on a change only the
12
+ * owner may make (#617): the file, why, and what to do. It says "test setup
13
+ * file" only when the file is one of the config's.
14
+ */
15
+ export const setupChangeDetail = ({
16
+ role,
17
+ file,
18
+ reason,
19
+ setup_files,
20
+ }: {
21
+ role: AgentRole
22
+ file: string
23
+ reason: string
24
+ /** The config's test setup files. */
25
+ setup_files: string[]
26
+ }): string =>
27
+ `The ${role} needs a change only you may make, to \`${file || 'unnamed'}\`: ${reason || 'no reason given'}\n` +
28
+ `${setup_files.includes(file) ? 'It is a test setup file, which agents may never change.' : 'No agent may make it.'} Make the change yourself in the worktree, then reply retry.`
29
+
30
+ /**
31
+ * A test sent back as bad, for a stuck detail: its file and name, then the
32
+ * reason (the implementer's, or the engine's finding that it checks nothing,
33
+ * #581), clipped like other outputs (#490). "no reason given" only when
34
+ * there is none.
35
+ *
36
+ * @example
37
+ * badTestDetail({ bad_test: { file: 'src/sum.test.ts', name: 'sum > adds', reason: 'Wrong sum.' } })
38
+ * // 'src/sum.test.ts > sum > adds: Wrong sum.'
39
+ */
40
+ export const badTestDetail = ({
41
+ bad_test,
42
+ }: {
43
+ bad_test: BadTest | null
44
+ }): string => {
45
+ const reason =
46
+ bad_test === null || bad_test.reason.trim() === ''
47
+ ? 'no reason given'
48
+ : clipOutput({ text: bad_test.reason })
49
+ const where =
50
+ bad_test === null
51
+ ? ''
52
+ : [bad_test.file, bad_test.name].filter(Boolean).join(' > ')
53
+ return where === '' ? reason : `${where}: ${reason}`
54
+ }
55
+
56
+ /** What each source did when it sent a test back as bad (#581). */
57
+ const BOUNCE_CLAUSES: Record<BadTestSource, string> = {
58
+ implementer: 'the implementer sent a test back as bad',
59
+ engine: 'the engine found a mapped test that checks nothing',
60
+ }
61
+
62
+ /**
63
+ * The start of a stuck detail for a test sent back as bad a second time
64
+ * (#581): who sent the first back, and who the second. A test can be sent
65
+ * back by the implementer, or by the engine when a mapped test passes with no
66
+ * `expect()`; the two share one cap, so the second may have another source
67
+ * than the first.
68
+ *
69
+ * @example
70
+ * badTestTwiceText({ earlier: 'engine', latest: 'implementer' })
71
+ * // 'The engine found a mapped test that checks nothing, and then, after a
72
+ * // fresh test-writer, the implementer sent a test back as bad'
73
+ */
74
+ export const badTestTwiceText = ({
75
+ earlier,
76
+ latest,
77
+ }: {
78
+ earlier: BadTestSource
79
+ latest: BadTestSource
80
+ }): string => {
81
+ if (earlier === latest) {
82
+ return latest === 'engine'
83
+ ? 'A mapped test still checks nothing after a fresh test-writer'
84
+ : 'The implementer sent a test back as bad a second time'
85
+ }
86
+ return upperFirst(
87
+ `${BOUNCE_CLAUSES[earlier]}, and then, after a fresh test-writer, ${BOUNCE_CLAUSES[latest]}`
88
+ )
89
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Formatting the stuck comments share: a clipped code fence for error text,
3
+ * and counted words.
4
+ */
5
+ import max from 'lodash/max'
6
+
7
+ /** Longest error text a comment or prompt quotes. */
8
+ const DETAIL_MAX = 3000
9
+
10
+ /** A code fence longer than any run of backticks in `text`. */
11
+ export const fenced = (text: string): string => {
12
+ const longest = max((text.match(/`+/g) ?? []).map(({ length }) => length))
13
+ const fence = '`'.repeat(Math.max(3, (longest ?? 0) + 1))
14
+ const clipped =
15
+ text.length > DETAIL_MAX
16
+ ? `${text.slice(0, DETAIL_MAX)}\n... (clipped)`
17
+ : text
18
+ return `${fence}\n${clipped}\n${fence}`
19
+ }
20
+
21
+ export const plural = (count: number, word: string): string =>
22
+ `${count} ${word}${count === 1 ? '' : 's'}`