@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.
- package/CHANGELOG.md +76 -0
- package/README.md +7 -2
- package/board/client/board-look.ts +31 -1
- package/board/client/board-panel-parts.tsx +150 -0
- package/board/client/board-panel-stages.ts +99 -0
- package/board/client/board-panel-styles.ts +223 -0
- package/board/client/board-panel.tsx +77 -1275
- package/board/client/engine-life.ts +48 -0
- package/board/client/final-review-stack.tsx +144 -0
- package/board/client/heartbeat.tsx +61 -0
- package/board/client/helper-lines.tsx +69 -0
- package/board/client/live-feed.tsx +86 -0
- package/board/client/live-line.ts +73 -0
- package/board/client/model-line.tsx +178 -0
- package/board/client/needs-you-section.tsx +133 -0
- package/board/client/one-line.tsx +193 -0
- package/board/client/repo-wait-banner.tsx +69 -0
- package/board/client/run-footer.tsx +68 -0
- package/board/client/run-header.tsx +189 -0
- package/board/client/run-picker.tsx +98 -0
- package/board/client/status-bar.tsx +131 -0
- package/board/client/step-dots.tsx +127 -0
- package/board/client/stuck-actions.tsx +269 -0
- package/board/client/ticket-card-view.tsx +238 -0
- package/board/client/ticket-detail.tsx +87 -0
- package/board/client/ticket-stack.tsx +119 -0
- package/board/client/ticket-wait.ts +188 -0
- package/board/client/timeline-rows.tsx +41 -14
- package/board/client/usage-line.tsx +51 -0
- package/board/client/wait-line.tsx +39 -0
- package/board/index.client.tsx +1 -1
- package/board/index.server.ts +7 -1
- package/board/package.json +1 -1
- package/board/server/board-panel-state-helpers.ts +67 -0
- package/board/server/board-server-check.ts +294 -0
- package/board/server/board-server-engine.ts +129 -0
- package/board/server/board-server-events.ts +134 -0
- package/board/server/board-server-read.ts +190 -0
- package/board/server/board-server-resume.ts +164 -0
- package/board/server/board-server-start.ts +174 -0
- package/board/server/board-server-store.ts +281 -0
- package/board/server/board-server-types.ts +114 -0
- package/board/server/board-server-util.ts +4 -0
- package/board/server/board-server.ts +52 -943
- package/board/server/board-vocabulary-engine.ts +144 -0
- package/board/server/board-vocabulary-review.ts +115 -0
- package/board/server/board-vocabulary-run.ts +145 -0
- package/board/server/board-vocabulary-shapes.ts +153 -0
- package/board/server/board-vocabulary.ts +20 -375
- package/board/server/current-step-panel-helpers.ts +108 -0
- package/board/server/describe-record.ts +403 -0
- package/board/server/engine-launch.ts +20 -3
- package/board/server/engine-restart-helpers.ts +105 -0
- package/board/server/engine-watch.ts +45 -5
- package/board/server/journal-rebuild-helpers.ts +169 -0
- package/board/server/live-file.ts +130 -0
- package/board/server/luca-version.ts +1 -1
- package/board/server/make-rows.ts +14 -629
- package/board/server/record-texts.ts +350 -0
- package/board/server/reduce-board-agent-start.ts +224 -0
- package/board/server/reduce-board-agents.ts +160 -0
- package/board/server/reduce-board-counters.ts +138 -0
- package/board/server/reduce-board-create.ts +148 -0
- package/board/server/reduce-board-final-review.ts +174 -0
- package/board/server/reduce-board-needs-you.ts +283 -0
- package/board/server/reduce-board-reasons.ts +59 -0
- package/board/server/reduce-board-run.ts +255 -0
- package/board/server/reduce-board-settle.ts +88 -0
- package/board/server/reduce-board-steps.ts +226 -0
- package/board/server/reduce-board-text.ts +139 -0
- package/board/server/reduce-board-ticket-checks.ts +168 -0
- package/board/server/reduce-board-ticket-join.ts +144 -0
- package/board/server/reduce-board-ticket-start.ts +132 -0
- package/board/server/reduce-board-update.ts +117 -0
- package/board/server/reduce-board-usage.ts +247 -0
- package/board/server/reduce-board.ts +100 -1917
- package/board/server/reply-post.ts +407 -0
- package/board/server/run-registry.ts +8 -4
- package/board/server/ticket-stages-panel-helpers.ts +296 -0
- package/board/shared/board-rows.ts +9 -0
- package/board/shared/board-rpc.ts +42 -0
- package/board/shared/board-state-counts.ts +77 -0
- package/board/shared/board-state-live.ts +99 -0
- package/board/shared/board-state-review.ts +59 -0
- package/board/shared/board-state-run.ts +236 -0
- package/board/shared/board-state-ticket.ts +257 -0
- package/board/shared/board-state-usage.ts +107 -0
- package/board/shared/board-state.ts +97 -567
- package/board/shared/reply-actions.ts +207 -0
- package/engine/agents/agent-launcher.ts +27 -4
- package/engine/agents/claude-launcher-helpers.ts +233 -0
- package/engine/agents/claude-launcher.ts +28 -669
- package/engine/agents/claude-messages.ts +212 -0
- package/engine/agents/claude-options.ts +43 -8
- package/engine/agents/common-instructions.ts +138 -0
- package/engine/agents/input-channel.ts +36 -0
- package/engine/agents/launch-paths.ts +29 -0
- package/engine/agents/launch-session.ts +227 -0
- package/engine/agents/learner-instructions.ts +31 -0
- package/engine/agents/open-session.ts +210 -0
- package/engine/agents/prompt-sections.ts +95 -0
- package/engine/agents/rejoin-prompts.ts +135 -0
- package/engine/agents/reviewer-instructions.ts +99 -0
- package/engine/agents/role-instructions.ts +31 -244
- package/engine/agents/role-prompts.ts +39 -109
- package/engine/agents/role-results.ts +106 -7
- package/engine/agents/scripted-launcher.ts +21 -6
- package/engine/agents/session-pool.ts +124 -0
- package/engine/agents/turn-progress.ts +283 -0
- package/engine/agents/writer-instructions.ts +151 -0
- package/engine/cli/computer-adapters-real.ts +15 -3
- package/engine/cli/computer-adapters.ts +2 -0
- package/engine/cli/computer-checks.ts +87 -4
- package/engine/cli/config-reload-text.ts +39 -0
- package/engine/cli/decision-model-setup.ts +70 -0
- package/engine/cli/doctor-command.ts +1 -0
- package/engine/cli/doctor.ts +21 -3
- package/engine/cli/going-runs.ts +192 -36
- package/engine/cli/init-command.ts +1 -0
- package/engine/cli/init.ts +23 -1
- package/engine/cli/live-runs.ts +73 -0
- package/engine/cli/luca-run.ts +42 -7
- package/engine/cli/luca-skills.ts +183 -0
- package/engine/cli/luca.ts +8 -1
- package/engine/cli/models-command.ts +119 -0
- package/engine/cli/read-run.ts +81 -0
- package/engine/cli/run-args.ts +33 -6
- package/engine/cli/run-demo.ts +143 -0
- package/engine/cli/run-end.ts +184 -0
- package/engine/cli/run-modes-helpers.ts +81 -0
- package/engine/cli/run-modes.ts +62 -439
- package/engine/cli/setup.ts +5 -48
- package/engine/cli/unfinished-runs.ts +122 -0
- package/engine/cli/upgrade-command.ts +212 -30
- package/engine/cli/upgrade-finish.ts +149 -0
- package/engine/cli/upgrade.ts +112 -71
- package/engine/config/config-fields.ts +127 -0
- package/engine/config/config-reload.ts +250 -0
- package/engine/config/engine-config-schema.ts +235 -0
- package/engine/config/engine-config.ts +72 -142
- package/engine/config/test-commands.ts +102 -0
- package/engine/core/agent-guards-helpers.ts +84 -0
- package/engine/core/agent-turn.ts +395 -0
- package/engine/core/already-done.ts +44 -1
- package/engine/core/build-action.ts +214 -0
- package/engine/core/build-context.ts +135 -0
- package/engine/core/build-launch.ts +259 -0
- package/engine/core/commit-in.ts +282 -0
- package/engine/core/decide-build-early.ts +108 -0
- package/engine/core/decide-build-ending.ts +145 -0
- package/engine/core/decide-build-helpers.ts +87 -0
- package/engine/core/decide-build-tickets.ts +207 -0
- package/engine/core/decide-build-view.ts +103 -0
- package/engine/core/decide-build.ts +101 -1368
- package/engine/core/decide-final-fix.ts +203 -0
- package/engine/core/decide-final-review.ts +55 -310
- package/engine/core/decide-red-green.ts +398 -0
- package/engine/core/decide-rejoin.ts +259 -0
- package/engine/core/decide-repo-wait.ts +129 -0
- package/engine/core/decide-run-budget.ts +86 -12
- package/engine/core/decide-stuck-helpers.ts +127 -0
- package/engine/core/decide-stuck.ts +60 -8
- package/engine/core/decide-ticket-review.ts +95 -0
- package/engine/core/decide-ticket.ts +267 -0
- package/engine/core/decide-waits.ts +47 -0
- package/engine/core/decide.ts +74 -23
- package/engine/core/engine-chores.ts +163 -0
- package/engine/core/environment-rerun.ts +83 -0
- package/engine/core/evidence-text.ts +84 -0
- package/engine/core/execute-action.ts +303 -0
- package/engine/core/execute-already-done-check.ts +133 -0
- package/engine/core/execute-build.ts +68 -944
- package/engine/core/execute-changeset.ts +5 -7
- package/engine/core/execute-final-review.ts +125 -13
- package/engine/core/execute-gates.ts +182 -0
- package/engine/core/execute-intake.ts +130 -0
- package/engine/core/execute-join.ts +276 -0
- package/engine/core/execute-memory.ts +12 -0
- package/engine/core/execute-pull-request.ts +84 -0
- package/engine/core/execute-red-check.ts +219 -0
- package/engine/core/execute-repo-wait.ts +129 -0
- package/engine/core/execute-stuck.ts +25 -10
- package/engine/core/execute-waits.ts +158 -0
- package/engine/core/execute-with-jev.ts +118 -0
- package/engine/core/execute.ts +124 -831
- package/engine/core/final-failure-cap.ts +38 -0
- package/engine/core/final-review-action.ts +125 -0
- package/engine/core/final-review-text.ts +63 -17
- package/engine/core/final-stuck-text.ts +86 -0
- package/engine/core/fix-loop-text.ts +49 -2
- package/engine/core/left-out.ts +120 -0
- package/engine/core/live-file.ts +372 -0
- package/engine/core/loop-caps.ts +25 -2
- package/engine/core/not-in-run.ts +7 -3
- package/engine/core/pull-request-text.ts +255 -6
- package/engine/core/repo-wait-text.ts +81 -0
- package/engine/core/review-text.ts +55 -1
- package/engine/core/run-crash-recovery-helpers.ts +16 -0
- package/engine/core/run-session-close-helpers.ts +96 -0
- package/engine/core/run-start.ts +148 -0
- package/engine/core/run-stuck-text.ts +86 -0
- package/engine/core/schedule-rules.ts +193 -0
- package/engine/core/stuck-detail-text.ts +89 -0
- package/engine/core/stuck-format.ts +22 -0
- package/engine/core/stuck-text.ts +29 -332
- package/engine/core/ticket-stuck-text.ts +192 -0
- package/engine/core/worktree-changes.ts +106 -0
- package/engine/gates/already-done-check.ts +120 -0
- package/engine/gates/feature-map.ts +96 -0
- package/engine/gates/gate-runner.ts +108 -108
- package/engine/gates/gate-schemas.ts +61 -2
- package/engine/gates/leftover-scan.ts +154 -31
- package/engine/gates/new-tests.ts +45 -0
- package/engine/gates/own-files-check.ts +98 -0
- package/engine/gates/prepare-check.ts +60 -0
- package/engine/gates/prepare-slots.ts +99 -0
- package/engine/gates/red-check.ts +184 -16
- package/engine/gates/shell-check.ts +64 -0
- package/engine/gates/slot-waits.ts +55 -0
- package/engine/gates/slow-test-runner.ts +59 -0
- package/engine/gates/test-checks.ts +161 -0
- package/engine/gates/test-runner.ts +164 -5
- package/engine/gates/test-slots.ts +48 -0
- package/engine/git/git-adapter.ts +55 -396
- package/engine/git/git-commits.ts +132 -0
- package/engine/git/git-join.ts +139 -0
- package/engine/git/git-read.ts +123 -0
- package/engine/git/git-run.ts +32 -0
- package/engine/git/git-worktrees.ts +143 -0
- package/engine/git/outside-links.ts +55 -0
- package/engine/guards/after-turn-check.ts +31 -5
- package/engine/guards/guard-hook.ts +60 -12
- package/engine/guards/guard-roles.ts +68 -0
- package/engine/guards/helper-rules.ts +92 -0
- package/engine/guards/permission-rules.ts +161 -0
- package/engine/guards/role-rules-helpers.ts +48 -0
- package/engine/guards/role-rules.ts +34 -606
- package/engine/guards/shell-rules.ts +344 -0
- package/engine/guards/worktree-git.ts +208 -0
- package/engine/guards/worktree-snapshot.ts +412 -0
- package/engine/guards/worktree-state.ts +28 -825
- package/engine/guards/worktree-undo.ts +260 -0
- package/engine/guards/write-rules.ts +149 -0
- package/engine/index.ts +30 -9
- package/engine/intake/criteria-checks.ts +114 -0
- package/engine/intake/intake-checks.ts +164 -30
- package/engine/intake/intake-schemas.ts +37 -2
- package/engine/jev/clef-client.ts +259 -0
- package/engine/jev/decision-model-credentials.ts +212 -0
- package/engine/jev/jev-jobs.ts +144 -60
- package/engine/jev/jev-schemas.ts +86 -5
- package/engine/jev/jev-shadow-helpers.ts +218 -0
- package/engine/jev/jev-shadow.ts +109 -64
- package/engine/jev/model-pick.ts +275 -0
- package/engine/journal/agent-entries.ts +167 -0
- package/engine/journal/agent-message-entries.ts +72 -0
- package/engine/journal/agent-turn-replay.ts +203 -0
- package/engine/journal/already-done-entries.ts +55 -0
- package/engine/journal/commit-entries.ts +56 -0
- package/engine/journal/cross-ticket-replay.ts +222 -0
- package/engine/journal/decision-model-entries.ts +86 -0
- package/engine/journal/entry-fields.ts +10 -0
- package/engine/journal/final-review-entries.ts +128 -0
- package/engine/journal/final-review-replay.ts +324 -0
- package/engine/journal/final-review-round.ts +320 -0
- package/engine/journal/final-review-state.ts +180 -0
- package/engine/journal/gate-entries.ts +81 -0
- package/engine/journal/intake-entries.ts +87 -0
- package/engine/journal/join-entries.ts +126 -0
- package/engine/journal/journal-entry-schema.ts +218 -0
- package/engine/journal/journal-kinds.ts +97 -0
- package/engine/journal/journal-record-schema.ts +218 -0
- package/engine/journal/journal-record.ts +43 -1400
- package/engine/journal/journal.ts +6 -1
- package/engine/journal/limit-entries.ts +90 -0
- package/engine/journal/memory-entries.ts +124 -0
- package/engine/journal/memory-replay.ts +157 -0
- package/engine/journal/plan-replay.ts +139 -0
- package/engine/journal/pull-request-entries.ts +41 -0
- package/engine/journal/replay.ts +157 -2144
- package/engine/journal/reply-replay.ts +144 -0
- package/engine/journal/repo-wait-entries.ts +53 -0
- package/engine/journal/run-entries.ts +172 -0
- package/engine/journal/run-state.ts +264 -0
- package/engine/journal/shared-failure-replay.ts +133 -0
- package/engine/journal/stuck-entries.ts +193 -0
- package/engine/journal/ticket-progress.ts +392 -0
- package/engine/journal/ticket-replay.ts +411 -0
- package/engine/journal/ticket-result-replay.ts +364 -0
- package/engine/journal/ticket-retry-replay.ts +142 -0
- package/engine/journal/worktree-entries.ts +122 -0
- package/engine/models/model-score.ts +512 -0
- package/engine/shell/run-command.ts +187 -18
- package/engine/testing/agent-turn-fixtures.ts +287 -0
- package/engine/testing/already-done-fixtures.ts +97 -0
- package/engine/testing/build-fixtures.ts +68 -973
- package/engine/testing/checkouts-fixtures.ts +48 -0
- package/engine/testing/final-review-fixtures.ts +62 -1
- package/engine/testing/final-review-steps.ts +143 -0
- package/engine/testing/limit-fixtures.ts +85 -0
- package/engine/testing/live-file-fixtures.ts +57 -0
- package/engine/testing/practice-repo.ts +10 -4
- package/engine/testing/practice-run.ts +1 -1
- package/engine/testing/reply-fixtures.ts +88 -0
- package/engine/testing/run-budget-fixtures.ts +165 -0
- package/engine/testing/run-fixtures.ts +219 -0
- package/engine/testing/sdk-play.ts +177 -0
- package/engine/testing/setup-repo.ts +194 -0
- package/engine/testing/step-record-fixtures.ts +55 -0
- package/engine/testing/ticket-step-fixtures.ts +272 -0
- package/engine/testing/wait-scenarios.ts +186 -0
- package/engine/tracker/gh-retry.ts +217 -0
- package/engine/tracker/github-tracker.ts +310 -90
- package/engine/tracker/in-memory-tracker.ts +8 -4
- package/engine/tracker/tracker.ts +131 -6
- package/package.json +2 -1
- package/skills/luca-retro/SKILL.md +74 -0
- package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
- package/skills/luca-unstick/SKILL.md +153 -0
- package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
- package/engine/jev/jev-client.ts +0 -173
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A ticket's test and code steps: the test-writer's tests, the red check and
|
|
3
|
+
* red commit, then the implementer's code, the gates and the green commit.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { alreadyDoneStep, checkAlreadyDoneStep } from './already-done'
|
|
7
|
+
import {
|
|
8
|
+
isRefactorTicket,
|
|
9
|
+
stuck,
|
|
10
|
+
type BuildAction,
|
|
11
|
+
type StepArgs,
|
|
12
|
+
} from './build-action'
|
|
13
|
+
import { launch } from './build-launch'
|
|
14
|
+
import { environmentFailedDetail } from './environment-rerun'
|
|
15
|
+
import { failedChecks, gateFixMessage, redFixMessage } from './fix-loop-text'
|
|
16
|
+
import { MAX_BAD_TEST_BOUNCES, MAX_FIX_ROUNDS } from './loop-caps'
|
|
17
|
+
import { badTestDetail, badTestTwiceText } from './stuck-text'
|
|
18
|
+
|
|
19
|
+
import { findCase } from '../gates/red-check'
|
|
20
|
+
import type { TicketSnapshot } from '../intake/intake-schemas'
|
|
21
|
+
import type { CommitStage } from '../journal/journal-record'
|
|
22
|
+
import type { TicketProgress } from '../journal/replay'
|
|
23
|
+
import type {
|
|
24
|
+
LoneRerunCheck,
|
|
25
|
+
SharedFailureFields,
|
|
26
|
+
} from '../journal/ticket-progress'
|
|
27
|
+
import { REFACTOR_LABEL } from '../tracker/tracker'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A red commit after a bad-test bounce says so, so the history shows it. A
|
|
31
|
+
* refactor ticket's one commit says it is a refactor.
|
|
32
|
+
*/
|
|
33
|
+
const commitMessage = ({
|
|
34
|
+
stage,
|
|
35
|
+
ticket,
|
|
36
|
+
progress,
|
|
37
|
+
}: {
|
|
38
|
+
stage: CommitStage
|
|
39
|
+
ticket: TicketSnapshot
|
|
40
|
+
progress: TicketProgress
|
|
41
|
+
}): string => {
|
|
42
|
+
if (stage === 'fix') {
|
|
43
|
+
return `fix: review round ${progress.review_fix?.round ?? progress.review_rounds} for #${ticket.number} ${ticket.title}`
|
|
44
|
+
}
|
|
45
|
+
if (stage === 'green') {
|
|
46
|
+
if (progress.rejoins > 0) {
|
|
47
|
+
return `fix: rejoin #${ticket.number} ${ticket.title} onto the run branch`
|
|
48
|
+
}
|
|
49
|
+
return isRefactorTicket({ ticket })
|
|
50
|
+
? `refactor: #${ticket.number} ${ticket.title}`
|
|
51
|
+
: `feat: build #${ticket.number} ${ticket.title}`
|
|
52
|
+
}
|
|
53
|
+
return progress.bad_test_bounces > 0
|
|
54
|
+
? `test: replace a bad test for #${ticket.number} ${ticket.title}`
|
|
55
|
+
: `test: add failing tests for #${ticket.number} ${ticket.title}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const commitStep = ({
|
|
59
|
+
stage,
|
|
60
|
+
ticket,
|
|
61
|
+
progress,
|
|
62
|
+
}: {
|
|
63
|
+
stage: CommitStage
|
|
64
|
+
ticket: TicketSnapshot
|
|
65
|
+
progress: TicketProgress
|
|
66
|
+
}): BuildAction | null => {
|
|
67
|
+
if (progress.commits[stage] !== null) return null
|
|
68
|
+
const hits = progress.leftovers[stage]
|
|
69
|
+
if (hits !== null && hits.length > 0) {
|
|
70
|
+
return stuck({
|
|
71
|
+
ticket: ticket.number,
|
|
72
|
+
reason: 'leftovers_found',
|
|
73
|
+
detail: hits
|
|
74
|
+
.map(({ path, reason }) => `${path}: ${reason}`)
|
|
75
|
+
.join('\n'),
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
type: 'commit_ticket',
|
|
80
|
+
ticket: ticket.number,
|
|
81
|
+
stage,
|
|
82
|
+
message: commitMessage({ stage, ticket, progress }),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether a failed red check or gates get a lone rerun (#544) before any
|
|
88
|
+
* fix round: every old test it failed also failed in another ticket within
|
|
89
|
+
* 10 minutes (replay marks it `shared`), and this check has had none yet.
|
|
90
|
+
*/
|
|
91
|
+
const loneRerunDue = ({
|
|
92
|
+
check,
|
|
93
|
+
progress,
|
|
94
|
+
which,
|
|
95
|
+
}: {
|
|
96
|
+
check: SharedFailureFields
|
|
97
|
+
progress: TicketProgress
|
|
98
|
+
which: LoneRerunCheck
|
|
99
|
+
}): boolean => check.shared === true && !progress.lone_reruns.includes(which)
|
|
100
|
+
|
|
101
|
+
/** The latest bad test, for a stuck detail (#490). */
|
|
102
|
+
export const badTestText = ({
|
|
103
|
+
progress,
|
|
104
|
+
}: {
|
|
105
|
+
progress: TicketProgress
|
|
106
|
+
}): string => badTestDetail({ bad_test: progress.bad_test })
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The test-writer's half of a ticket: fresh tests, the red check and its fix
|
|
110
|
+
* loop, then the red commit. `null` once the red commit is made. A ticket
|
|
111
|
+
* whose work the test-writer found already on the base branch is marked
|
|
112
|
+
* done instead (#484); one that changes no behavior is stuck, as a refactor
|
|
113
|
+
* ticket without its label.
|
|
114
|
+
*/
|
|
115
|
+
export const testStep = ({
|
|
116
|
+
snapshot,
|
|
117
|
+
ticket,
|
|
118
|
+
progress,
|
|
119
|
+
run_notes,
|
|
120
|
+
base_branch,
|
|
121
|
+
}: StepArgs & { base_branch: string }): BuildAction | null => {
|
|
122
|
+
const number = ticket.number
|
|
123
|
+
const { test_writer, red_check } = progress
|
|
124
|
+
if (test_writer === null) {
|
|
125
|
+
return launch({
|
|
126
|
+
role: 'test-writer',
|
|
127
|
+
snapshot,
|
|
128
|
+
ticket,
|
|
129
|
+
progress,
|
|
130
|
+
run_notes,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
if (test_writer.outcome === 'already_done') {
|
|
134
|
+
// Its evidence is checked first (#495). A failed check is a failed
|
|
135
|
+
// try, which `failedTurnStep` takes before this step; checking the
|
|
136
|
+
// same evidence again would only loop, so one left here is stuck.
|
|
137
|
+
const check = progress.already_done_check
|
|
138
|
+
if (check === null) {
|
|
139
|
+
return checkAlreadyDoneStep({
|
|
140
|
+
ticket,
|
|
141
|
+
base_sha: progress.worktree?.base_sha ?? 'HEAD',
|
|
142
|
+
result: test_writer,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
if (!check.ok) {
|
|
146
|
+
return stuck({
|
|
147
|
+
ticket: number,
|
|
148
|
+
reason: 'agent_failed',
|
|
149
|
+
detail: `The test-writer's "already_done" evidence did not check out:\n${check.problems.map((problem) => `- ${problem}`).join('\n')}`,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
return alreadyDoneStep({
|
|
153
|
+
spec_number: snapshot.spec.number,
|
|
154
|
+
base_branch,
|
|
155
|
+
ticket,
|
|
156
|
+
result: test_writer,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
if (test_writer.outcome === 'nothing_new_to_test') {
|
|
160
|
+
return stuck({
|
|
161
|
+
ticket: number,
|
|
162
|
+
reason: 'nothing_new_to_test',
|
|
163
|
+
detail:
|
|
164
|
+
`The test-writer says this ticket changes no behavior, so no new test can fail first: ${test_writer.summary || 'no reason given'}\n` +
|
|
165
|
+
`A ticket like that is a refactor ticket: add the \`${REFACTOR_LABEL}\` label and retry it.`,
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
const redCheck: Extract<BuildAction, { type: 'run_red_check' }> = {
|
|
169
|
+
type: 'run_red_check',
|
|
170
|
+
ticket: number,
|
|
171
|
+
criteria_ids: ticket.criteria.map(({ id }) => id),
|
|
172
|
+
mapping: test_writer.criteria,
|
|
173
|
+
}
|
|
174
|
+
// Its old tests failed in another ticket too: run it alone first, even
|
|
175
|
+
// after an `environment` rerun, before that can make it stuck.
|
|
176
|
+
if (
|
|
177
|
+
red_check !== null &&
|
|
178
|
+
!red_check.ok &&
|
|
179
|
+
loneRerunDue({ check: red_check, progress, which: 'red_check' })
|
|
180
|
+
) {
|
|
181
|
+
return { ...redCheck, exclusive: true }
|
|
182
|
+
}
|
|
183
|
+
const blamed = environmentFailedDetail({
|
|
184
|
+
environment:
|
|
185
|
+
progress.environment?.check === 'red_check'
|
|
186
|
+
? progress.environment
|
|
187
|
+
: null,
|
|
188
|
+
reruns: progress.environment_reruns,
|
|
189
|
+
latest: red_check,
|
|
190
|
+
})
|
|
191
|
+
if (blamed !== null) {
|
|
192
|
+
return stuck({
|
|
193
|
+
ticket: number,
|
|
194
|
+
reason: 'environment_failed',
|
|
195
|
+
detail: blamed,
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
if (red_check === null) return redCheck
|
|
199
|
+
if (!red_check.ok) {
|
|
200
|
+
const problems = red_check.problems.join('\n')
|
|
201
|
+
if (progress.red_fix_rounds >= MAX_FIX_ROUNDS) {
|
|
202
|
+
return stuck({
|
|
203
|
+
ticket: number,
|
|
204
|
+
reason: 'red_check_failed',
|
|
205
|
+
detail: `The red check still fails after ${MAX_FIX_ROUNDS} fix rounds:\n${problems}`,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
const session_id = progress.sessions['test-writer']
|
|
209
|
+
const message = redFixMessage({ red_check })
|
|
210
|
+
if (session_id === undefined) {
|
|
211
|
+
return launch({
|
|
212
|
+
role: 'test-writer',
|
|
213
|
+
snapshot,
|
|
214
|
+
ticket,
|
|
215
|
+
progress,
|
|
216
|
+
run_notes,
|
|
217
|
+
follow_up: message,
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
type: 'follow_up_agent',
|
|
222
|
+
ticket: number,
|
|
223
|
+
role: 'test-writer',
|
|
224
|
+
session_id,
|
|
225
|
+
message,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return commitStep({ stage: 'red', ticket, progress })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The implementer's half of a ticket: the code, the gates and their fix
|
|
233
|
+
* loop, then the green commit. `null` once the green commit is made.
|
|
234
|
+
*/
|
|
235
|
+
export const codeStep = ({
|
|
236
|
+
snapshot,
|
|
237
|
+
ticket,
|
|
238
|
+
progress,
|
|
239
|
+
run_notes,
|
|
240
|
+
}: StepArgs): BuildAction | null => {
|
|
241
|
+
const number = ticket.number
|
|
242
|
+
const { implementer } = progress
|
|
243
|
+
if (implementer === null) {
|
|
244
|
+
return launch({
|
|
245
|
+
role: 'implementer',
|
|
246
|
+
snapshot,
|
|
247
|
+
ticket,
|
|
248
|
+
progress,
|
|
249
|
+
run_notes,
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
if (implementer.outcome === 'bad_test') {
|
|
253
|
+
if (isRefactorTicket({ ticket })) {
|
|
254
|
+
return stuck({
|
|
255
|
+
ticket: number,
|
|
256
|
+
reason: 'bad_test',
|
|
257
|
+
detail: `The implementer of a refactor ticket sent a test back as bad; there is no test-writer to fix it: ${badTestText({ progress })}`,
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
if (progress.bad_test_bounces > MAX_BAD_TEST_BOUNCES) {
|
|
261
|
+
return stuck({
|
|
262
|
+
ticket: number,
|
|
263
|
+
reason: 'bad_test',
|
|
264
|
+
detail: `${badTestTwiceText({ earlier: progress.bad_test_sources.at(-2) ?? 'implementer', latest: 'implementer' })}: ${badTestText({ progress })}`,
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
// Throw away the implementer's work; replay then clears the ticket's
|
|
268
|
+
// tests and code, so a fresh test-writer replaces the bad test.
|
|
269
|
+
return { type: 'reset_ticket_worktree', ticket: number }
|
|
270
|
+
}
|
|
271
|
+
return (
|
|
272
|
+
gateStep({ snapshot, ticket, progress, run_notes }) ??
|
|
273
|
+
emptyTestStep({ ticket, progress }) ??
|
|
274
|
+
commitStep({ stage: 'green', ticket, progress })
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Why the engine sends back a mapped test that checks nothing (#581). */
|
|
279
|
+
export const EMPTY_TEST_REASON =
|
|
280
|
+
'It passes but runs no expect(), so it checks nothing (#581).'
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* After a ticket's gates pass: a mapped test that passed with no `expect()`
|
|
284
|
+
* goes back to a fresh test-writer, like a bad test (#581), and counts toward
|
|
285
|
+
* the same cap. Not on a refactor ticket (it has no map), and not after a
|
|
286
|
+
* rebase (the code is already reviewed). An older journal's cases carry no
|
|
287
|
+
* count, so nothing is sent back. `null`: go on.
|
|
288
|
+
*
|
|
289
|
+
* It is not part of `gateStep`, which the review's fixes also use: their
|
|
290
|
+
* tests are not red-checked.
|
|
291
|
+
*/
|
|
292
|
+
const emptyTestStep = ({
|
|
293
|
+
ticket,
|
|
294
|
+
progress,
|
|
295
|
+
}: {
|
|
296
|
+
ticket: TicketSnapshot
|
|
297
|
+
progress: TicketProgress
|
|
298
|
+
}): BuildAction | null => {
|
|
299
|
+
if (isRefactorTicket({ ticket }) || progress.rejoin !== null) return null
|
|
300
|
+
const cases = progress.gates?.cases ?? []
|
|
301
|
+
const empty = progress.red_tests.find((test) => {
|
|
302
|
+
const found = findCase({ cases, test })
|
|
303
|
+
return found?.status === 'passed' && found.assertions === 0
|
|
304
|
+
})
|
|
305
|
+
if (empty === undefined) return null
|
|
306
|
+
if (progress.bad_test_bounces >= MAX_BAD_TEST_BOUNCES) {
|
|
307
|
+
return stuck({
|
|
308
|
+
ticket: ticket.number,
|
|
309
|
+
reason: 'bad_test',
|
|
310
|
+
detail: `${badTestTwiceText({ earlier: progress.bad_test_sources.at(-1) ?? 'implementer', latest: 'engine' })}: "${empty.name}" in ${empty.file} runs no expect().`,
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
type: 'reset_ticket_worktree',
|
|
315
|
+
ticket: ticket.number,
|
|
316
|
+
bad_test: {
|
|
317
|
+
file: empty.file,
|
|
318
|
+
name: empty.name,
|
|
319
|
+
reason: EMPTY_TEST_REASON,
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The ticket's gates and their fix loop: failed gates go back to the same
|
|
326
|
+
* implementer session (a fresh one with the same message if it is gone),
|
|
327
|
+
* up to `MAX_FIX_ROUNDS` follow-ups. An `environment` answer reruns them
|
|
328
|
+
* without a fix round; a rerun that fails the same way, or one past
|
|
329
|
+
* `MAX_ENVIRONMENT_RERUNS`, is stuck (#613). `null` once the gates pass.
|
|
330
|
+
*/
|
|
331
|
+
export const gateStep = ({
|
|
332
|
+
snapshot,
|
|
333
|
+
ticket,
|
|
334
|
+
progress,
|
|
335
|
+
run_notes,
|
|
336
|
+
}: StepArgs): BuildAction | null => {
|
|
337
|
+
const number = ticket.number
|
|
338
|
+
const { gates } = progress
|
|
339
|
+
// Its old tests failed in another ticket too: run them alone first, even
|
|
340
|
+
// after an `environment` rerun, before that can make it stuck.
|
|
341
|
+
if (
|
|
342
|
+
gates !== null &&
|
|
343
|
+
!gates.ok &&
|
|
344
|
+
loneRerunDue({ check: gates, progress, which: 'gates' })
|
|
345
|
+
) {
|
|
346
|
+
return {
|
|
347
|
+
type: 'run_gates',
|
|
348
|
+
ticket: number,
|
|
349
|
+
target: 'ticket',
|
|
350
|
+
exclusive: true,
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const blamed = environmentFailedDetail({
|
|
354
|
+
environment:
|
|
355
|
+
progress.environment?.check === 'gates'
|
|
356
|
+
? progress.environment
|
|
357
|
+
: null,
|
|
358
|
+
reruns: progress.environment_reruns,
|
|
359
|
+
latest: gates,
|
|
360
|
+
})
|
|
361
|
+
if (blamed !== null) {
|
|
362
|
+
return stuck({
|
|
363
|
+
ticket: number,
|
|
364
|
+
reason: 'environment_failed',
|
|
365
|
+
detail: blamed,
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
if (gates === null) {
|
|
369
|
+
return { type: 'run_gates', ticket: number, target: 'ticket' }
|
|
370
|
+
}
|
|
371
|
+
if (gates.ok) return null
|
|
372
|
+
if (progress.gate_fix_rounds >= MAX_FIX_ROUNDS) {
|
|
373
|
+
return stuck({
|
|
374
|
+
ticket: number,
|
|
375
|
+
reason: 'gates_failed',
|
|
376
|
+
detail: `The gates still fail after ${MAX_FIX_ROUNDS} fix rounds:\n${failedChecks({ gates })}`,
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
const session_id = progress.sessions.implementer
|
|
380
|
+
const message = gateFixMessage({ gates })
|
|
381
|
+
if (session_id === undefined) {
|
|
382
|
+
return launch({
|
|
383
|
+
role: 'implementer',
|
|
384
|
+
snapshot,
|
|
385
|
+
ticket,
|
|
386
|
+
progress,
|
|
387
|
+
run_notes,
|
|
388
|
+
follow_up: message,
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
type: 'follow_up_agent',
|
|
393
|
+
ticket: number,
|
|
394
|
+
role: 'implementer',
|
|
395
|
+
session_id,
|
|
396
|
+
message,
|
|
397
|
+
}
|
|
398
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A ticket's join and rebase steps: joining the run branch, and fixing the
|
|
3
|
+
* ticket on top of it after a clash or failed gates, with its test updates.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
isRefactorTicket,
|
|
8
|
+
stuck,
|
|
9
|
+
type BuildAction,
|
|
10
|
+
type StepArgs,
|
|
11
|
+
} from './build-action'
|
|
12
|
+
import { launch } from './build-launch'
|
|
13
|
+
import { badTestText } from './decide-red-green'
|
|
14
|
+
import {
|
|
15
|
+
clashFixMessage,
|
|
16
|
+
failedChecks,
|
|
17
|
+
testsUpdatedMessage,
|
|
18
|
+
} from './fix-loop-text'
|
|
19
|
+
import { MAX_REJOINS, MAX_TEST_UPDATES } from './loop-caps'
|
|
20
|
+
import { badTestDetail } from './stuck-text'
|
|
21
|
+
|
|
22
|
+
import type { TicketSnapshot } from '../intake/intake-schemas'
|
|
23
|
+
import type { ReplayedWorktree, TicketProgress } from '../journal/replay'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The fix on top of the run branch after a rebase: first a fresh test-writer
|
|
27
|
+
* for clashed tests, then the implementer for clashed code. `null` once both
|
|
28
|
+
* have answered; the gates, the green commit, and a fresh review of the new
|
|
29
|
+
* changes follow as usual.
|
|
30
|
+
*
|
|
31
|
+
* A bad test here can't reset the worktree, which would throw the ticket's
|
|
32
|
+
* uncommitted change away. Instead its tests go back to the test-writer
|
|
33
|
+
* (`send_tests_back`, #489), up to `MAX_TEST_UPDATES` times per ticket, then
|
|
34
|
+
* it is stuck. A refactor ticket has no test-writer, so it is stuck at once.
|
|
35
|
+
*/
|
|
36
|
+
export const rejoinStep = ({
|
|
37
|
+
snapshot,
|
|
38
|
+
ticket,
|
|
39
|
+
progress,
|
|
40
|
+
run_notes,
|
|
41
|
+
}: StepArgs): BuildAction | null => {
|
|
42
|
+
const { rejoin } = progress
|
|
43
|
+
if (rejoin === null) return null
|
|
44
|
+
if (progress.test_update !== null) {
|
|
45
|
+
return testUpdateStep({ snapshot, ticket, progress, run_notes })
|
|
46
|
+
}
|
|
47
|
+
if (progress.implementer?.outcome === 'bad_test') {
|
|
48
|
+
return sendTestsBackStep({ ticket, progress, rejoin })
|
|
49
|
+
}
|
|
50
|
+
if (rejoin.tests_pending) {
|
|
51
|
+
return launch({
|
|
52
|
+
role: 'test-writer',
|
|
53
|
+
snapshot,
|
|
54
|
+
ticket,
|
|
55
|
+
progress,
|
|
56
|
+
run_notes,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
if (rejoin.code_pending) {
|
|
60
|
+
const session_id = progress.sessions.implementer
|
|
61
|
+
if (session_id === undefined) {
|
|
62
|
+
return launch({
|
|
63
|
+
role: 'implementer',
|
|
64
|
+
snapshot,
|
|
65
|
+
ticket,
|
|
66
|
+
progress,
|
|
67
|
+
run_notes,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
type: 'follow_up_agent',
|
|
72
|
+
ticket: ticket.number,
|
|
73
|
+
role: 'implementer',
|
|
74
|
+
session_id,
|
|
75
|
+
message: clashFixMessage({ rejoin }),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A bad test after a rebase: its tests go back to the test-writer, or, past
|
|
83
|
+
* the cap or on a refactor ticket, it is stuck with the implementer's reason.
|
|
84
|
+
*/
|
|
85
|
+
const sendTestsBackStep = ({
|
|
86
|
+
ticket,
|
|
87
|
+
progress,
|
|
88
|
+
rejoin,
|
|
89
|
+
}: {
|
|
90
|
+
ticket: TicketSnapshot
|
|
91
|
+
progress: TicketProgress
|
|
92
|
+
rejoin: NonNullable<TicketProgress['rejoin']>
|
|
93
|
+
}): BuildAction => {
|
|
94
|
+
const number = ticket.number
|
|
95
|
+
if (isRefactorTicket({ ticket })) {
|
|
96
|
+
return stuck({
|
|
97
|
+
ticket: number,
|
|
98
|
+
reason: 'bad_test',
|
|
99
|
+
detail: `The implementer of a refactor ticket sent a test back as bad while fixing the ticket on top of the run branch; there is no test-writer to update it: ${badTestText({ progress })}`,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
if (progress.test_updates >= MAX_TEST_UPDATES) {
|
|
103
|
+
return stuck({
|
|
104
|
+
ticket: number,
|
|
105
|
+
reason: 'bad_test',
|
|
106
|
+
detail: `The implementer sent a test back as bad on top of the run branch after the test-writer updated the tests ${MAX_TEST_UPDATES} times: ${badTestText({ progress })}`,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
type: 'send_tests_back',
|
|
111
|
+
ticket: number,
|
|
112
|
+
round: progress.test_updates + 1,
|
|
113
|
+
bad_test: progress.bad_test ?? {
|
|
114
|
+
file: '',
|
|
115
|
+
name: '',
|
|
116
|
+
reason: progress.implementer?.summary ?? '',
|
|
117
|
+
},
|
|
118
|
+
from_sha: rejoin.from_sha,
|
|
119
|
+
base_sha: rejoin.base_sha,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The open test update after a rebase (#489): a fresh test-writer updates
|
|
125
|
+
* the tests (its prompt says what joined and why the test is wrong), then
|
|
126
|
+
* the implementer finishes the ticket (a follow-up in its session, or a
|
|
127
|
+
* fresh one). There is no red check here: the implementer's code is already
|
|
128
|
+
* in the worktree, so the updated tests may pass, and the red commit already
|
|
129
|
+
* proved the ticket's tests failed first. The test-writer's mapping must
|
|
130
|
+
* still give every criterion a test; then the gates run every test, the
|
|
131
|
+
* updated ones and the run branch's together, and a fresh reviewer checks
|
|
132
|
+
* the whole change.
|
|
133
|
+
*/
|
|
134
|
+
const testUpdateStep = ({
|
|
135
|
+
snapshot,
|
|
136
|
+
ticket,
|
|
137
|
+
progress,
|
|
138
|
+
run_notes,
|
|
139
|
+
}: StepArgs): BuildAction => {
|
|
140
|
+
const update = progress.test_update
|
|
141
|
+
if (update === null || update.result === null) {
|
|
142
|
+
return launch({
|
|
143
|
+
role: 'test-writer',
|
|
144
|
+
snapshot,
|
|
145
|
+
ticket,
|
|
146
|
+
progress,
|
|
147
|
+
run_notes,
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
const { result } = update
|
|
151
|
+
if (result.outcome !== 'tests_written') {
|
|
152
|
+
return stuck({
|
|
153
|
+
ticket: ticket.number,
|
|
154
|
+
reason: 'bad_test',
|
|
155
|
+
detail:
|
|
156
|
+
`The implementer sent a test back as bad on top of the run branch, and the test-writer answered "${result.outcome}" instead of updating the tests: ${result.summary || 'no reason given'}\n` +
|
|
157
|
+
`The bad test: ${badTestDetail({ bad_test: update.bad_test })}`,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
const unmapped = ticket.criteria
|
|
161
|
+
.map(({ id }) => id)
|
|
162
|
+
.filter(
|
|
163
|
+
(id) =>
|
|
164
|
+
!result.criteria.some(
|
|
165
|
+
({ criterion_id, tests }) =>
|
|
166
|
+
criterion_id === id && tests.length > 0
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
if (unmapped.length > 0) {
|
|
170
|
+
return stuck({
|
|
171
|
+
ticket: ticket.number,
|
|
172
|
+
reason: 'red_check_failed',
|
|
173
|
+
detail: `After updating the tests on top of the run branch, the test-writer mapped no test to ${unmapped.join(', ')}. Every criterion needs a test.`,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
const session_id = progress.sessions.implementer
|
|
177
|
+
if (session_id === undefined) {
|
|
178
|
+
return launch({
|
|
179
|
+
role: 'implementer',
|
|
180
|
+
snapshot,
|
|
181
|
+
ticket,
|
|
182
|
+
progress,
|
|
183
|
+
run_notes,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
type: 'follow_up_agent',
|
|
188
|
+
ticket: ticket.number,
|
|
189
|
+
role: 'implementer',
|
|
190
|
+
session_id,
|
|
191
|
+
message: testsUpdatedMessage({
|
|
192
|
+
bad_test: update.bad_test,
|
|
193
|
+
summary: result.summary,
|
|
194
|
+
}),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The join section of an approved ticket, for the first ticket in the join
|
|
200
|
+
* queue only: join, the gates on the run branch, the push. A clash or failed
|
|
201
|
+
* gates sends the ticket back onto the run branch, up to `MAX_REJOINS`
|
|
202
|
+
* times; after that it is stuck. `null` once pushed.
|
|
203
|
+
*/
|
|
204
|
+
export const joinStep = ({
|
|
205
|
+
ticket,
|
|
206
|
+
progress,
|
|
207
|
+
run_branch,
|
|
208
|
+
}: {
|
|
209
|
+
ticket: TicketSnapshot
|
|
210
|
+
progress: TicketProgress
|
|
211
|
+
run_branch: ReplayedWorktree
|
|
212
|
+
}): BuildAction | null => {
|
|
213
|
+
const number = ticket.number
|
|
214
|
+
const { joined, join_gates } = progress
|
|
215
|
+
if (joined === null) {
|
|
216
|
+
return { type: 'join_run_branch', ticket: number }
|
|
217
|
+
}
|
|
218
|
+
if (!joined.ok) {
|
|
219
|
+
if (progress.rejoins >= MAX_REJOINS) {
|
|
220
|
+
return stuck({
|
|
221
|
+
ticket: number,
|
|
222
|
+
reason: 'join_failed',
|
|
223
|
+
detail: `The ticket still clashes with the run branch after ${MAX_REJOINS} rebases:\n${joined.error}`,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
type: 'rebase_ticket',
|
|
228
|
+
ticket: number,
|
|
229
|
+
cause: 'clash',
|
|
230
|
+
undo_first_sha: null,
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (join_gates === null) {
|
|
234
|
+
return { type: 'run_gates', ticket: number, target: 'run_branch' }
|
|
235
|
+
}
|
|
236
|
+
if (!join_gates.ok) {
|
|
237
|
+
if (progress.rejoins >= MAX_REJOINS) {
|
|
238
|
+
return stuck({
|
|
239
|
+
ticket: number,
|
|
240
|
+
reason: 'join_gates_failed',
|
|
241
|
+
detail: `The gates still fail after joining, after ${MAX_REJOINS} rebases:\n${failedChecks({ gates: join_gates })}`,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
type: 'rebase_ticket',
|
|
246
|
+
ticket: number,
|
|
247
|
+
cause: 'join_gates',
|
|
248
|
+
undo_first_sha: joined.shas[0] ?? null,
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (progress.pushed === null) {
|
|
252
|
+
return {
|
|
253
|
+
type: 'push_run_branch',
|
|
254
|
+
ticket: number,
|
|
255
|
+
branch: run_branch.branch,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return null
|
|
259
|
+
}
|