@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,129 @@
|
|
|
1
|
+
import { decideRunBudget, type RunBudgetAction } from './decide-run-budget'
|
|
2
|
+
import type { StuckAction } from './decide-stuck'
|
|
3
|
+
|
|
4
|
+
import type { GoingRun } from '../cli/going-runs'
|
|
5
|
+
import type { JournalRecord } from '../journal/journal-record'
|
|
6
|
+
import type { RunState } from '../journal/replay'
|
|
7
|
+
|
|
8
|
+
/** How long a repo wait lasts before the run is stuck (`repo_busy`). */
|
|
9
|
+
export const REPO_WAIT_MAX_MS = 24 * 60 * 60 * 1000
|
|
10
|
+
|
|
11
|
+
/** How long each nap of a repo wait is, between looks at the going runs. */
|
|
12
|
+
export const REPO_WAIT_NAP_MS = 60_000
|
|
13
|
+
|
|
14
|
+
/** An earlier run on the repo that a repo wait waits for. */
|
|
15
|
+
export type RepoWaitRun = { run_id: string; spec: number | null }
|
|
16
|
+
|
|
17
|
+
/** The run's steps for the repo wait (#615), before intake. */
|
|
18
|
+
export type RepoWaitAction =
|
|
19
|
+
/** Tell the spec issue which runs the run waits for, and journal it. */
|
|
20
|
+
| { type: 'start_repo_wait'; spec_number: number; runs: RepoWaitRun[] }
|
|
21
|
+
/**
|
|
22
|
+
* Nap and look again until no earlier run is going, then journal the
|
|
23
|
+
* wait's end; at `until`, the run is stuck as `repo_busy`.
|
|
24
|
+
*/
|
|
25
|
+
| { type: 'wait_for_repo'; spec_number: number; until: string }
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The going runs a repo wait waits for: those on the same repo as this
|
|
29
|
+
* run, started earlier by `run_started` time, then by run id, so two runs
|
|
30
|
+
* started together never wait for each other. A run with no repo (an older
|
|
31
|
+
* journal) or no start time never counts, and neither does this run. Pure.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* earlierRuns({ runs: going.runs, run_id: 'run-b', records })
|
|
35
|
+
* // [{ run_id: 'run-a', spec: 7 }]
|
|
36
|
+
*/
|
|
37
|
+
export const earlierRuns = ({
|
|
38
|
+
runs,
|
|
39
|
+
run_id,
|
|
40
|
+
records,
|
|
41
|
+
}: {
|
|
42
|
+
runs: Pick<GoingRun, 'run_id' | 'spec' | 'repo' | 'started_at'>[]
|
|
43
|
+
/** This run's id. */
|
|
44
|
+
run_id: string
|
|
45
|
+
/** This run's journal. */
|
|
46
|
+
records: JournalRecord[]
|
|
47
|
+
}): RepoWaitRun[] => {
|
|
48
|
+
const start = records.find((record) => record.kind === 'run_started')
|
|
49
|
+
if (start?.kind !== 'run_started' || start.content.repo === null) return []
|
|
50
|
+
const { repo } = start.content
|
|
51
|
+
const own = Date.parse(start.time)
|
|
52
|
+
return runs
|
|
53
|
+
.filter((run) => {
|
|
54
|
+
if (run.repo !== repo || run.run_id === run_id) return false
|
|
55
|
+
if (run.started_at === null) return false
|
|
56
|
+
const at = Date.parse(run.started_at)
|
|
57
|
+
return at < own || (at === own && run.run_id < run_id)
|
|
58
|
+
})
|
|
59
|
+
.map(({ run_id: id, spec }) => ({ run_id: id, spec }))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether the run has yet to look for earlier runs on its repo: intake
|
|
64
|
+
* hasn't read the tickets, and no repo wait began or was skipped. Pure, and
|
|
65
|
+
* cheap: it reads only the record kinds.
|
|
66
|
+
*/
|
|
67
|
+
export const repoWaitUnchecked = ({
|
|
68
|
+
records,
|
|
69
|
+
}: {
|
|
70
|
+
records: JournalRecord[]
|
|
71
|
+
}): boolean =>
|
|
72
|
+
records.length > 0 &&
|
|
73
|
+
!records.some(
|
|
74
|
+
({ kind }) =>
|
|
75
|
+
kind === 'intake_read' ||
|
|
76
|
+
kind === 'repo_wait_started' ||
|
|
77
|
+
kind === 'repo_wait_skipped'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The repo wait half of the decision step, before intake (#615). Pure. A
|
|
82
|
+
* run with `earlier_runs` going on its repo waits for them: it tells the
|
|
83
|
+
* spec issue once, then naps until none is going. A run that waited 24
|
|
84
|
+
* hours is stuck (`repo_busy`) until the owner's `retry` (another 24
|
|
85
|
+
* hours) or `stop`. A skipped or ended wait, and a run with no earlier
|
|
86
|
+
* runs (or none given), go on to intake: `null`.
|
|
87
|
+
*/
|
|
88
|
+
export const decideRepoWait = ({
|
|
89
|
+
state,
|
|
90
|
+
records,
|
|
91
|
+
spec_number,
|
|
92
|
+
earlier_runs,
|
|
93
|
+
}: {
|
|
94
|
+
state: RunState
|
|
95
|
+
records: JournalRecord[]
|
|
96
|
+
spec_number: number
|
|
97
|
+
/** The earlier runs going on the repo; left out, there is no wait. */
|
|
98
|
+
earlier_runs: RepoWaitRun[] | undefined
|
|
99
|
+
}):
|
|
100
|
+
| (
|
|
101
|
+
| RepoWaitAction
|
|
102
|
+
| RunBudgetAction
|
|
103
|
+
| StuckAction
|
|
104
|
+
| { type: 'done'; outcome: 'stopped_by_user' }
|
|
105
|
+
)[]
|
|
106
|
+
| null => {
|
|
107
|
+
if (state.phase !== 'started') return null
|
|
108
|
+
if (state.stop !== null) {
|
|
109
|
+
return [{ type: 'done', outcome: 'stopped_by_user' }]
|
|
110
|
+
}
|
|
111
|
+
if (state.run_budget.stuck !== null) {
|
|
112
|
+
return decideRunBudget({ state, records, spec_number })
|
|
113
|
+
}
|
|
114
|
+
const { status, since } = state.repo_wait
|
|
115
|
+
if (status === 'waiting' && since !== null) {
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
type: 'wait_for_repo',
|
|
119
|
+
spec_number,
|
|
120
|
+
until: new Date(
|
|
121
|
+
Date.parse(since) + REPO_WAIT_MAX_MS
|
|
122
|
+
).toISOString(),
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
if (status !== 'none' || earlier_runs === undefined) return null
|
|
127
|
+
if (earlier_runs.length === 0) return null
|
|
128
|
+
return [{ type: 'start_repo_wait', spec_number, runs: earlier_runs }]
|
|
129
|
+
}
|
|
@@ -1,10 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
amountTokens,
|
|
3
|
+
pendingReplies,
|
|
4
|
+
waitForReply,
|
|
5
|
+
type StuckAction,
|
|
6
|
+
} from './decide-stuck'
|
|
7
|
+
import { repoBusyStuckComment } from './repo-wait-text'
|
|
8
|
+
import {
|
|
9
|
+
pullRequestStuckComment,
|
|
10
|
+
replyAnswer,
|
|
11
|
+
runBudgetDetail,
|
|
12
|
+
runStuckComment,
|
|
13
|
+
} from './stuck-text'
|
|
3
14
|
|
|
4
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
JournalRecord,
|
|
17
|
+
ReplyProblem,
|
|
18
|
+
RunStuckReason,
|
|
19
|
+
} from '../journal/journal-record'
|
|
5
20
|
import type { RunState } from '../journal/replay'
|
|
6
21
|
import { runBudgetOf, runTokensIn } from '../limits/run-budget'
|
|
7
22
|
|
|
23
|
+
/** The spec issue's comment for the stuck run, by why it is stuck. */
|
|
24
|
+
const stuckBody = ({
|
|
25
|
+
stuck,
|
|
26
|
+
budget,
|
|
27
|
+
}: {
|
|
28
|
+
stuck: { reason: RunStuckReason; detail: string }
|
|
29
|
+
budget: number
|
|
30
|
+
}): string => {
|
|
31
|
+
switch (stuck.reason) {
|
|
32
|
+
case 'pull_request_failed':
|
|
33
|
+
return pullRequestStuckComment({ detail: stuck.detail })
|
|
34
|
+
case 'repo_busy':
|
|
35
|
+
return repoBusyStuckComment({ detail: stuck.detail })
|
|
36
|
+
case 'run_budget':
|
|
37
|
+
return runStuckComment({ detail: stuck.detail, budget })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
8
41
|
/** The run's steps with its budget of tokens (#435). */
|
|
9
42
|
export type RunBudgetAction =
|
|
10
43
|
/** The run used up its budget: journal it as stuck. */
|
|
@@ -15,11 +48,16 @@ export type RunBudgetAction =
|
|
|
15
48
|
/**
|
|
16
49
|
* The run budget half of the decision step. Pure. Once the run's tokens
|
|
17
50
|
* (`runTokensIn`) reach its budget (the config's `run_budget_tokens`, else
|
|
18
|
-
* the default,
|
|
19
|
-
* reason "run budget": that is its only step, and nothing new starts.
|
|
20
|
-
* spec issue is told, then the run waits for the owner: a bare `retry`
|
|
21
|
-
*
|
|
22
|
-
*
|
|
51
|
+
* the default, plus every top-up a `retry` granted), the run is stuck with
|
|
52
|
+
* the reason "run budget": that is its only step, and nothing new starts.
|
|
53
|
+
* The spec issue is told, then the run waits for the owner: a bare `retry`
|
|
54
|
+
* tops the budget up by half a budget, rounded down, and `retry +3M` by the
|
|
55
|
+
* amount named (#543; replay adds the top-up); `stop` ends the run. A zero
|
|
56
|
+
* or unreadable amount is sent back (`bad_amount`). The owner's other
|
|
57
|
+
* replies wait until the run carries on. A run whose PR didn't open (#508)
|
|
58
|
+
* or whose repo wait ran out is stuck the same way, told why, and a bare
|
|
59
|
+
* `retry` tries again, adding no budget; an amount there is sent back
|
|
60
|
+
* (`amount_only_for_run_budget`). `null` when
|
|
23
61
|
* the budget has nothing to say: the run is under it, stopping, or its PR
|
|
24
62
|
* is open.
|
|
25
63
|
*
|
|
@@ -37,11 +75,11 @@ export const decideRunBudget = ({
|
|
|
37
75
|
spec_number: number
|
|
38
76
|
}): (RunBudgetAction | StuckAction)[] | null => {
|
|
39
77
|
if (state.stop !== null || state.pull_request !== null) return null
|
|
40
|
-
const { stuck, report,
|
|
78
|
+
const { stuck, report, top_ups } = state.run_budget
|
|
41
79
|
const budget = runBudgetOf({ config: state.config })
|
|
42
80
|
if (stuck === null) {
|
|
43
81
|
const tokens = runTokensIn({ records })
|
|
44
|
-
const allowed = budget
|
|
82
|
+
const allowed = budget + top_ups
|
|
45
83
|
if (tokens < allowed) return null
|
|
46
84
|
return [
|
|
47
85
|
{
|
|
@@ -56,7 +94,7 @@ export const decideRunBudget = ({
|
|
|
56
94
|
{
|
|
57
95
|
type: 'report_run_stuck',
|
|
58
96
|
spec_number,
|
|
59
|
-
body:
|
|
97
|
+
body: stuckBody({ stuck, budget }),
|
|
60
98
|
},
|
|
61
99
|
]
|
|
62
100
|
}
|
|
@@ -65,12 +103,48 @@ export const decideRunBudget = ({
|
|
|
65
103
|
word === 'stop' || (word === 'retry' && ticket === null)
|
|
66
104
|
)
|
|
67
105
|
if (reply === undefined) return [waitForReply({ state, spec_number })]
|
|
106
|
+
const ignore = (reason: ReplyProblem): StuckAction[] => [
|
|
107
|
+
{
|
|
108
|
+
type: 'ignore_reply',
|
|
109
|
+
comment_id: reply.comment_id,
|
|
110
|
+
spec_number,
|
|
111
|
+
reason,
|
|
112
|
+
answer: replyAnswer({ problem: reason, named: null, stuck: [] }),
|
|
113
|
+
},
|
|
114
|
+
]
|
|
115
|
+
if (reply.word === 'stop') {
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
type: 'take_reply',
|
|
119
|
+
comment_id: reply.comment_id,
|
|
120
|
+
word: 'stop',
|
|
121
|
+
ticket: null,
|
|
122
|
+
},
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
if (stuck.reason !== 'run_budget') {
|
|
126
|
+
if (reply.amount !== null) return ignore('amount_only_for_run_budget')
|
|
127
|
+
return [
|
|
128
|
+
{
|
|
129
|
+
type: 'take_reply',
|
|
130
|
+
comment_id: reply.comment_id,
|
|
131
|
+
word: 'retry',
|
|
132
|
+
ticket: null,
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
const top_up =
|
|
137
|
+
reply.amount === null
|
|
138
|
+
? Math.floor(budget / 2)
|
|
139
|
+
: amountTokens({ amount: reply.amount })
|
|
140
|
+
if (top_up === null) return ignore('bad_amount')
|
|
68
141
|
return [
|
|
69
142
|
{
|
|
70
143
|
type: 'take_reply',
|
|
71
144
|
comment_id: reply.comment_id,
|
|
72
|
-
word:
|
|
145
|
+
word: 'retry',
|
|
73
146
|
ticket: null,
|
|
147
|
+
top_up,
|
|
74
148
|
},
|
|
75
149
|
]
|
|
76
150
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { decideSteps } from './decide'
|
|
2
|
+
import { MAX_FIX_ROUNDS } from './decide-build'
|
|
3
|
+
|
|
4
|
+
import type { TicketSnapshot } from '../intake/intake-schemas'
|
|
5
|
+
import type { JournalEntry } from '../journal/journal-record'
|
|
6
|
+
import {
|
|
7
|
+
agentStarted,
|
|
8
|
+
baselineTests,
|
|
9
|
+
gatesRun,
|
|
10
|
+
implemented,
|
|
11
|
+
intakePassed,
|
|
12
|
+
practiceTicket,
|
|
13
|
+
SESSIONS,
|
|
14
|
+
ticketBuilt,
|
|
15
|
+
ticketStuck,
|
|
16
|
+
worktreeReset,
|
|
17
|
+
ticketWorktreeCreated,
|
|
18
|
+
withInstalls,
|
|
19
|
+
} from '../testing/build-fixtures'
|
|
20
|
+
import { recordsFrom } from '../testing/intake-fixtures'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fixtures shared by the decide-stuck tests (#366): the tickets, the helpers
|
|
24
|
+
* that decide the next steps, and journals of a ticket stuck for good.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export const SUM = practiceTicket({ number: 11, title: 'Add sum' })
|
|
28
|
+
export const PRODUCT = practiceTicket({ number: 12, title: 'Add product' })
|
|
29
|
+
export const AVERAGE = practiceTicket({
|
|
30
|
+
number: 13,
|
|
31
|
+
title: 'Add average',
|
|
32
|
+
blockers: [11],
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export const stepsAfter = ({
|
|
36
|
+
tickets,
|
|
37
|
+
entries,
|
|
38
|
+
}: {
|
|
39
|
+
tickets: TicketSnapshot[]
|
|
40
|
+
entries: JournalEntry[]
|
|
41
|
+
}) =>
|
|
42
|
+
decideSteps({
|
|
43
|
+
records: recordsFrom({
|
|
44
|
+
entries: [
|
|
45
|
+
...intakePassed({ tickets }),
|
|
46
|
+
...withInstalls({ entries }),
|
|
47
|
+
],
|
|
48
|
+
}),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const twoSteps = (entries: JournalEntry[]) =>
|
|
52
|
+
stepsAfter({ tickets: [SUM, PRODUCT], entries })
|
|
53
|
+
|
|
54
|
+
export const oneSteps = (entries: JournalEntry[]) =>
|
|
55
|
+
stepsAfter({ tickets: [SUM], entries })
|
|
56
|
+
|
|
57
|
+
/** #11 failed its gates after every fix round. */
|
|
58
|
+
export const gatesFailedForGood = (): JournalEntry[] => [
|
|
59
|
+
ticketWorktreeCreated({ ticket: 11 }),
|
|
60
|
+
baselineTests({ ticket: 11 }),
|
|
61
|
+
...ticketBuilt({ ticket: 11 }).slice(2, 6),
|
|
62
|
+
implemented({ ticket: 11 }),
|
|
63
|
+
gatesRun({ ticket: 11, target: 'ticket', ok: false }),
|
|
64
|
+
...Array.from({ length: MAX_FIX_ROUNDS }, () => [
|
|
65
|
+
agentStarted({
|
|
66
|
+
ticket: 11,
|
|
67
|
+
role: 'implementer',
|
|
68
|
+
follow_up_of: SESSIONS.implementer,
|
|
69
|
+
}),
|
|
70
|
+
implemented({ ticket: 11 }),
|
|
71
|
+
gatesRun({ ticket: 11, target: 'ticket', ok: false }),
|
|
72
|
+
]).flat(),
|
|
73
|
+
ticketStuck({
|
|
74
|
+
ticket: 11,
|
|
75
|
+
reason: 'gates_failed',
|
|
76
|
+
detail: 'The gates still fail after 3 fix rounds:\ntest: 1 fail',
|
|
77
|
+
}),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* #11's one mapped test passed with no expect() twice (#581): the engine sent
|
|
82
|
+
* it back once, and the second time the ticket is stuck as `bad_test`.
|
|
83
|
+
*/
|
|
84
|
+
export const emptyTestForGood = (): JournalEntry[] => {
|
|
85
|
+
const emptyGates = gatesRun({
|
|
86
|
+
ticket: 11,
|
|
87
|
+
target: 'ticket',
|
|
88
|
+
ok: true,
|
|
89
|
+
cases: [
|
|
90
|
+
{
|
|
91
|
+
file: 'src/sum.test.ts',
|
|
92
|
+
full_name: 'sum adds two numbers',
|
|
93
|
+
status: 'passed',
|
|
94
|
+
assertions: 0,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
})
|
|
98
|
+
return [
|
|
99
|
+
ticketWorktreeCreated({ ticket: 11 }),
|
|
100
|
+
baselineTests({ ticket: 11 }),
|
|
101
|
+
...ticketBuilt({ ticket: 11 }).slice(2, 6),
|
|
102
|
+
implemented({ ticket: 11 }),
|
|
103
|
+
emptyGates,
|
|
104
|
+
worktreeReset({
|
|
105
|
+
ticket: 11,
|
|
106
|
+
bad_test: {
|
|
107
|
+
file: 'src/sum.test.ts',
|
|
108
|
+
name: 'sum adds two numbers',
|
|
109
|
+
reason: 'It passes but runs no expect(), so it checks nothing (#581).',
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
...ticketBuilt({ ticket: 11 }).slice(2, 6),
|
|
113
|
+
implemented({ ticket: 11, session_id: 'impl-2' }),
|
|
114
|
+
emptyGates,
|
|
115
|
+
ticketStuck({
|
|
116
|
+
ticket: 11,
|
|
117
|
+
reason: 'bad_test',
|
|
118
|
+
detail: 'A mapped test still checks nothing after a fresh test-writer.',
|
|
119
|
+
}),
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** #12 has its worktree and baseline: its test-writer is next. */
|
|
124
|
+
export const productStarted = (): JournalEntry[] => [
|
|
125
|
+
ticketWorktreeCreated({ ticket: 12 }),
|
|
126
|
+
baselineTests({ ticket: 12 }),
|
|
127
|
+
]
|
|
@@ -32,12 +32,16 @@ export type StuckAction =
|
|
|
32
32
|
* work waits for a reply; other tickets keep building meanwhile.
|
|
33
33
|
*/
|
|
34
34
|
| { type: 'wait_for_reply'; spec_number: number; since_id: number }
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The owner's comment is a reply: journal it. A `retry` of the run stuck
|
|
37
|
+
* on its budget carries the tokens it adds (`top_up`, #543).
|
|
38
|
+
*/
|
|
36
39
|
| {
|
|
37
40
|
type: 'take_reply'
|
|
38
41
|
comment_id: number
|
|
39
42
|
word: ReplyWord
|
|
40
43
|
ticket: number | null
|
|
44
|
+
top_up?: number
|
|
41
45
|
}
|
|
42
46
|
/** The owner's comment looks like a reply but can't be used: say why. */
|
|
43
47
|
| {
|
|
@@ -70,24 +74,64 @@ export type StuckAction =
|
|
|
70
74
|
body: string
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
/**
|
|
74
|
-
|
|
77
|
+
/**
|
|
78
|
+
* A reply word, the ticket it names if any, and the text after `retry +`
|
|
79
|
+
* (`amount`, #543) if any, from a comment's text.
|
|
80
|
+
*/
|
|
81
|
+
type ParsedReply = {
|
|
82
|
+
word: ReplyWord | 'ship'
|
|
83
|
+
ticket: number | null
|
|
84
|
+
amount: string | null
|
|
85
|
+
}
|
|
75
86
|
|
|
76
87
|
const REPLY_PATTERN = /^(retry|skip|stop|ship)(?:\s+#?(\d+))?[\s.!]*$/i
|
|
77
88
|
|
|
89
|
+
const AMOUNT_REPLY_PATTERN = /^retry\s+\+(\S*?)[\s.!]*$/i
|
|
90
|
+
|
|
78
91
|
/**
|
|
79
92
|
* Reads a one-word reply: `retry`, `skip`, `stop`, or `ship`, optionally
|
|
80
|
-
* naming a ticket (`retry #12`, `skip 12`)
|
|
93
|
+
* naming a ticket (`retry #12`, `skip 12`), or `retry` with an amount
|
|
94
|
+
* (`retry +3M`). Anything else is not a reply.
|
|
81
95
|
*
|
|
82
96
|
* @example
|
|
83
|
-
* parseReply({ body: 'Retry #12' }) // { word: 'retry', ticket: 12 }
|
|
97
|
+
* parseReply({ body: 'Retry #12' }) // { word: 'retry', ticket: 12, amount: null }
|
|
98
|
+
* parseReply({ body: 'retry +3M' }) // { word: 'retry', ticket: null, amount: '3M' }
|
|
84
99
|
* parseReply({ body: 'Looking at it.' }) // null
|
|
85
100
|
*/
|
|
86
101
|
export const parseReply = ({ body }: { body: string }): ParsedReply | null => {
|
|
102
|
+
const amount = AMOUNT_REPLY_PATTERN.exec(body.trim())
|
|
103
|
+
if (amount !== null) {
|
|
104
|
+
return { word: 'retry', ticket: null, amount: amount[1] ?? '' }
|
|
105
|
+
}
|
|
87
106
|
const match = REPLY_PATTERN.exec(body.trim())
|
|
88
107
|
if (match === null) return null
|
|
89
108
|
const word = match[1]?.toLowerCase() as ParsedReply['word']
|
|
90
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
word,
|
|
111
|
+
ticket: match[2] === undefined ? null : Number(match[2]),
|
|
112
|
+
amount: null,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const AMOUNT_PATTERN = /^(\d+)([km]?)$/i
|
|
117
|
+
|
|
118
|
+
const AMOUNT_SCALES: Record<string, number> = { '': 1, k: 1_000, m: 1_000_000 }
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The tokens an amount after `retry +` names: a whole number, optionally
|
|
122
|
+
* followed by `k` or `M` in any case. `null` for zero, or for anything that
|
|
123
|
+
* can't be read.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* amountTokens({ amount: '500k' }) // 500_000
|
|
127
|
+
* amountTokens({ amount: '1.5M' }) // null
|
|
128
|
+
*/
|
|
129
|
+
export const amountTokens = ({ amount }: { amount: string }): number | null => {
|
|
130
|
+
const match = AMOUNT_PATTERN.exec(amount)
|
|
131
|
+
if (match === null) return null
|
|
132
|
+
const scale = AMOUNT_SCALES[(match[2] ?? '').toLowerCase()] ?? 1
|
|
133
|
+
const tokens = Number(match[1]) * scale
|
|
134
|
+
return tokens > 0 && Number.isSafeInteger(tokens) ? tokens : null
|
|
91
135
|
}
|
|
92
136
|
|
|
93
137
|
/**
|
|
@@ -137,7 +181,9 @@ const awaitingReply = (progress: TicketProgress): boolean =>
|
|
|
137
181
|
/**
|
|
138
182
|
* The next steps of one stuck ticket: undo a join its stuck left on the run
|
|
139
183
|
* branch (so no other ticket builds on it), tell the spec issue, then act
|
|
140
|
-
* on the owner's reply. Nothing while it waits for one.
|
|
184
|
+
* on the owner's reply. Nothing while it waits for one. A ticket stuck
|
|
185
|
+
* because its undo was refused (`undo_refused`, #519) is never undone again
|
|
186
|
+
* before a reply: it is told at once, with its join left where it is.
|
|
141
187
|
*/
|
|
142
188
|
export const stuckTicketSteps = ({
|
|
143
189
|
spec_number,
|
|
@@ -151,7 +197,11 @@ export const stuckTicketSteps = ({
|
|
|
151
197
|
const { stuck, joined } = progress
|
|
152
198
|
if (stuck === null) return []
|
|
153
199
|
const number = ticket.number
|
|
154
|
-
if (
|
|
200
|
+
if (
|
|
201
|
+
joined?.ok === true &&
|
|
202
|
+
progress.pushed === null &&
|
|
203
|
+
stuck.reason !== 'undo_refused'
|
|
204
|
+
) {
|
|
155
205
|
const first_sha = joined.shas[0]
|
|
156
206
|
if (first_sha !== undefined) {
|
|
157
207
|
return [{ type: 'undo_join', ticket: number, first_sha }]
|
|
@@ -315,6 +365,8 @@ export const replySteps = ({
|
|
|
315
365
|
},
|
|
316
366
|
]
|
|
317
367
|
}
|
|
368
|
+
// Only the run stuck on its budget takes an amount (decideRunBudget).
|
|
369
|
+
if (next.amount !== null) return ignore('amount_only_for_run_budget')
|
|
318
370
|
if (next.ticket !== null && !waiting.includes(next.ticket)) {
|
|
319
371
|
return ignore('not_stuck')
|
|
320
372
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A ticket's review step: a fresh reviewer after the green commit, and the
|
|
3
|
+
* review's fix rounds, each re-reviewed.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { stuck, type BuildAction, type StepArgs } from './build-action'
|
|
7
|
+
import { launch } from './build-launch'
|
|
8
|
+
import { commitStep, gateStep } from './decide-red-green'
|
|
9
|
+
import { MAX_FIX_ROUNDS } from './loop-caps'
|
|
10
|
+
import { openFindingsText, reviewFixMessage } from './review-text'
|
|
11
|
+
import { badTestDetail } from './stuck-text'
|
|
12
|
+
|
|
13
|
+
import { handoffRole } from '../journal/replay'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The ticket review and its fix loop, after the green commit. A fresh
|
|
17
|
+
* reviewer checks the committed diff. Blockers and should-fixes open a fix
|
|
18
|
+
* round: test findings go to a fresh test-writer first, then code findings
|
|
19
|
+
* to the same implementer session (a fresh one if it is gone), then the
|
|
20
|
+
* findings a fixer answered `other_role` to a fresh agent of the other
|
|
21
|
+
* writer role (#616). The fixes
|
|
22
|
+
* pass the gates, get their own commit, and a fresh reviewer checks only
|
|
23
|
+
* the new changes. A review that still asks for changes after
|
|
24
|
+
* `MAX_FIX_ROUNDS` fix rounds is stuck. `null` once a review approves.
|
|
25
|
+
*/
|
|
26
|
+
export const reviewStep = ({
|
|
27
|
+
snapshot,
|
|
28
|
+
ticket,
|
|
29
|
+
progress,
|
|
30
|
+
run_notes,
|
|
31
|
+
}: StepArgs): BuildAction | null => {
|
|
32
|
+
const number = ticket.number
|
|
33
|
+
const reviewer = () =>
|
|
34
|
+
launch({
|
|
35
|
+
role: 'ticket-reviewer',
|
|
36
|
+
snapshot,
|
|
37
|
+
ticket,
|
|
38
|
+
progress,
|
|
39
|
+
run_notes,
|
|
40
|
+
})
|
|
41
|
+
if (progress.review === null) return reviewer()
|
|
42
|
+
const fix = progress.review_fix
|
|
43
|
+
if (fix === null) return null
|
|
44
|
+
if (fix.round > MAX_FIX_ROUNDS) {
|
|
45
|
+
return stuck({
|
|
46
|
+
ticket: number,
|
|
47
|
+
reason: 'changes_requested',
|
|
48
|
+
detail: `The ticket review still asks for changes after ${MAX_FIX_ROUNDS} fix rounds:\n${openFindingsText({ findings: fix.findings })}`,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
if (!fix.tests_answered) {
|
|
52
|
+
return launch({
|
|
53
|
+
role: 'test-writer',
|
|
54
|
+
snapshot,
|
|
55
|
+
ticket,
|
|
56
|
+
progress,
|
|
57
|
+
run_notes,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
if (!fix.code_answered) {
|
|
61
|
+
const session_id = progress.sessions.implementer
|
|
62
|
+
if (session_id === undefined) {
|
|
63
|
+
return launch({
|
|
64
|
+
role: 'implementer',
|
|
65
|
+
snapshot,
|
|
66
|
+
ticket,
|
|
67
|
+
progress,
|
|
68
|
+
run_notes,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
type: 'follow_up_agent',
|
|
73
|
+
ticket: number,
|
|
74
|
+
role: 'implementer',
|
|
75
|
+
session_id,
|
|
76
|
+
message: reviewFixMessage({ fix }),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (fix.bad_test !== null) {
|
|
80
|
+
return stuck({
|
|
81
|
+
ticket: number,
|
|
82
|
+
reason: 'bad_test',
|
|
83
|
+
detail: `While fixing review findings, the implementer sent a test back as bad: ${badTestDetail({ bad_test: fix.bad_test })}`,
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
const handoff = handoffRole({ fix })
|
|
87
|
+
if (handoff !== null) {
|
|
88
|
+
return launch({ role: handoff, snapshot, ticket, progress, run_notes })
|
|
89
|
+
}
|
|
90
|
+
return (
|
|
91
|
+
gateStep({ snapshot, ticket, progress, run_notes }) ??
|
|
92
|
+
commitStep({ stage: 'fix', ticket, progress }) ??
|
|
93
|
+
reviewer()
|
|
94
|
+
)
|
|
95
|
+
}
|