@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,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The build steps that settle the run before any ticket moves: a `stop`
|
|
3
|
+
* reply or an open PR winds the run down (`haltSteps`), and the run branch's
|
|
4
|
+
* install (`installSteps`), which a failure holds every ticket back on. Pure.
|
|
5
|
+
*/
|
|
6
|
+
import { stuck, type BuildAction } from './build-action'
|
|
7
|
+
import { notRemoved, type BuildView } from './decide-build-view'
|
|
8
|
+
import { installFailure } from './decide-ticket'
|
|
9
|
+
|
|
10
|
+
import type { RunState } from '../journal/replay'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The steps that end the run (a `stop` reply, or an open PR), or `null` when
|
|
14
|
+
* the run goes on.
|
|
15
|
+
*/
|
|
16
|
+
export const haltSteps = ({
|
|
17
|
+
state,
|
|
18
|
+
view,
|
|
19
|
+
}: {
|
|
20
|
+
state: RunState
|
|
21
|
+
view: BuildView
|
|
22
|
+
}): BuildAction[] | null => {
|
|
23
|
+
const { run_branch, numbers, progress, finished } = view
|
|
24
|
+
const { pull_request } = state
|
|
25
|
+
if (state.stop !== null) {
|
|
26
|
+
// Nothing new starts; finished tickets' worktrees go, the rest stay.
|
|
27
|
+
const paths = notRemoved({
|
|
28
|
+
state,
|
|
29
|
+
paths: numbers.flatMap((number) => {
|
|
30
|
+
const { worktree } = progress(number)
|
|
31
|
+
return finished(number) && worktree !== null
|
|
32
|
+
? [worktree.path]
|
|
33
|
+
: []
|
|
34
|
+
}),
|
|
35
|
+
})
|
|
36
|
+
if (paths.length > 0) return [{ type: 'remove_worktrees', paths }]
|
|
37
|
+
return [{ type: 'done', outcome: 'stopped_by_user' }]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (pull_request !== null) {
|
|
41
|
+
const paths = notRemoved({
|
|
42
|
+
state,
|
|
43
|
+
paths: [
|
|
44
|
+
...numbers.flatMap((number) => {
|
|
45
|
+
const { worktree } = progress(number)
|
|
46
|
+
return worktree === null ? [] : [worktree.path]
|
|
47
|
+
}),
|
|
48
|
+
run_branch.path,
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
if (paths.length > 0) return [{ type: 'remove_worktrees', paths }]
|
|
52
|
+
return [{ type: 'done', outcome: 'pr_opened', pull_request }]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The run branch's install, or the stuck ticket a failed one holds the run on
|
|
60
|
+
* (with the owner's `replies`), or `null` when the run branch is installed.
|
|
61
|
+
*/
|
|
62
|
+
export const installSteps = ({
|
|
63
|
+
state,
|
|
64
|
+
view,
|
|
65
|
+
replies,
|
|
66
|
+
}: {
|
|
67
|
+
state: RunState
|
|
68
|
+
view: BuildView
|
|
69
|
+
replies: BuildAction[]
|
|
70
|
+
}): BuildAction[] | null => {
|
|
71
|
+
const { numbers, finished, skipped, isStuck, stuckSteps } = view
|
|
72
|
+
const { run_branch_install } = state
|
|
73
|
+
if (run_branch_install === null) {
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
type: 'install_dependencies',
|
|
77
|
+
target: 'run_branch',
|
|
78
|
+
ticket: null,
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
const runBranchFailure = installFailure({
|
|
83
|
+
install: run_branch_install,
|
|
84
|
+
where: "the run branch's checkout",
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (runBranchFailure !== null) {
|
|
88
|
+
// Nothing builds on a run branch whose install failed: the first
|
|
89
|
+
// ticket is stuck on it until a retry installs it again.
|
|
90
|
+
if (numbers.some(isStuck)) {
|
|
91
|
+
return [...numbers.filter(isStuck).flatMap(stuckSteps), ...replies]
|
|
92
|
+
}
|
|
93
|
+
const first = numbers.find(
|
|
94
|
+
(number) => !finished(number) && !skipped(number)
|
|
95
|
+
)
|
|
96
|
+
if (first !== undefined) {
|
|
97
|
+
return [
|
|
98
|
+
stuck({
|
|
99
|
+
ticket: first,
|
|
100
|
+
reason: 'install_failed',
|
|
101
|
+
detail: runBranchFailure,
|
|
102
|
+
}),
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The build steps once no ticket can move: closing the already-done tickets
|
|
3
|
+
* when none joined, the final review, the changeset, and the PR
|
|
4
|
+
* (`finishSteps`). Pure.
|
|
5
|
+
*/
|
|
6
|
+
import { closeTicketStep } from './already-done'
|
|
7
|
+
import type { BuildAction } from './build-action'
|
|
8
|
+
import { changesetStep } from './changeset'
|
|
9
|
+
import { notRemoved, type BuildView } from './decide-build-view'
|
|
10
|
+
import { decideFinalReview } from './decide-final-review'
|
|
11
|
+
import { finalStuckSteps } from './decide-stuck'
|
|
12
|
+
import { pullRequestText } from './pull-request-text'
|
|
13
|
+
|
|
14
|
+
import type { RunState } from '../journal/replay'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The steps when no ticket can move now: the already-done tickets closed, the
|
|
18
|
+
* final review, the changeset, and the PR. `replies` are the owner's replies
|
|
19
|
+
* a stuck final review takes.
|
|
20
|
+
*/
|
|
21
|
+
export const finishSteps = ({
|
|
22
|
+
state,
|
|
23
|
+
view,
|
|
24
|
+
replies,
|
|
25
|
+
}: {
|
|
26
|
+
state: RunState
|
|
27
|
+
view: BuildView
|
|
28
|
+
replies: BuildAction[]
|
|
29
|
+
}): BuildAction[] => {
|
|
30
|
+
const {
|
|
31
|
+
spec_number,
|
|
32
|
+
snapshot,
|
|
33
|
+
run_branch,
|
|
34
|
+
base_branch,
|
|
35
|
+
run_notes,
|
|
36
|
+
numbers,
|
|
37
|
+
progress,
|
|
38
|
+
pushed,
|
|
39
|
+
alreadyDone,
|
|
40
|
+
finished,
|
|
41
|
+
skipped,
|
|
42
|
+
} = view
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
numbers.every((number) => finished(number) || skipped(number)) &&
|
|
46
|
+
!numbers.some(pushed)
|
|
47
|
+
) {
|
|
48
|
+
// No ticket joined the run branch: no PR, and nothing left to build.
|
|
49
|
+
// The engine closes the already-done tickets itself (#484).
|
|
50
|
+
const closing = numbers.filter(
|
|
51
|
+
(number) => alreadyDone(number) && !progress(number).closed
|
|
52
|
+
)
|
|
53
|
+
if (closing.length > 0) {
|
|
54
|
+
return closing.map((number) =>
|
|
55
|
+
closeTicketStep({
|
|
56
|
+
spec_number,
|
|
57
|
+
base_branch,
|
|
58
|
+
ticket: number,
|
|
59
|
+
shas: progress(number).already_done?.shas ?? [],
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
const paths = notRemoved({
|
|
64
|
+
state,
|
|
65
|
+
paths: [
|
|
66
|
+
...numbers.flatMap((number) => {
|
|
67
|
+
const { worktree } = progress(number)
|
|
68
|
+
return worktree === null ? [] : [worktree.path]
|
|
69
|
+
}),
|
|
70
|
+
run_branch.path,
|
|
71
|
+
],
|
|
72
|
+
})
|
|
73
|
+
if (paths.length > 0) return [{ type: 'remove_worktrees', paths }]
|
|
74
|
+
if (numbers.some(skipped)) {
|
|
75
|
+
return [{ type: 'done', outcome: 'all_skipped' }]
|
|
76
|
+
}
|
|
77
|
+
// Every ticket's work was already done: nothing to do.
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
type: 'finish_nothing_to_do',
|
|
81
|
+
closed_tickets: snapshot.closed_tickets,
|
|
82
|
+
already_done: numbers.filter(alreadyDone),
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
if (numbers.every((number) => finished(number) || skipped(number))) {
|
|
87
|
+
const final = decideFinalReview({
|
|
88
|
+
review: state.final_review,
|
|
89
|
+
crashes: state.crashes,
|
|
90
|
+
snapshot,
|
|
91
|
+
run_branch,
|
|
92
|
+
run_branch_gates: state.run_branch_gates,
|
|
93
|
+
run_notes,
|
|
94
|
+
setup_files: state.config?.test_setup_files ?? [],
|
|
95
|
+
})
|
|
96
|
+
if (final.status === 'working') return final.actions
|
|
97
|
+
if (final.status === 'stuck') {
|
|
98
|
+
// The run branch's worktree stays for a retry.
|
|
99
|
+
const paths = notRemoved({
|
|
100
|
+
state,
|
|
101
|
+
paths: numbers.flatMap((number) => {
|
|
102
|
+
const { worktree } = progress(number)
|
|
103
|
+
return worktree === null ? [] : [worktree.path]
|
|
104
|
+
}),
|
|
105
|
+
})
|
|
106
|
+
if (paths.length > 0) return [{ type: 'remove_worktrees', paths }]
|
|
107
|
+
// The owner hears of it, and the run waits for their reply.
|
|
108
|
+
return [
|
|
109
|
+
...finalStuckSteps({ state, spec_number, run_branch }),
|
|
110
|
+
...replies,
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
if (state.changesets && state.changeset === null) {
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
type: 'write_changeset',
|
|
117
|
+
branch: run_branch.branch,
|
|
118
|
+
...changesetStep({ spec: snapshot.spec }),
|
|
119
|
+
},
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
type: 'open_pull_request',
|
|
125
|
+
head: run_branch.branch,
|
|
126
|
+
base: base_branch,
|
|
127
|
+
...pullRequestText({
|
|
128
|
+
snapshot,
|
|
129
|
+
tickets: state.tickets,
|
|
130
|
+
final_review: state.final_review,
|
|
131
|
+
memory_saves: state.memory.saved?.saves ?? [],
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
return [
|
|
137
|
+
{
|
|
138
|
+
type: 'invalid_journal',
|
|
139
|
+
reason: `No ticket can move: ${numbers
|
|
140
|
+
.filter((number) => !finished(number))
|
|
141
|
+
.map((number) => `#${number}`)
|
|
142
|
+
.join(', ')} wait on tickets that never join.`,
|
|
143
|
+
},
|
|
144
|
+
]
|
|
145
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { decide } from './decide'
|
|
2
|
+
|
|
3
|
+
import type { JournalEntry } from '../journal/journal-record'
|
|
4
|
+
import {
|
|
5
|
+
baselineTests,
|
|
6
|
+
commitMade,
|
|
7
|
+
implemented,
|
|
8
|
+
intakePassed,
|
|
9
|
+
leftoverScan,
|
|
10
|
+
practiceTicket,
|
|
11
|
+
redCheck,
|
|
12
|
+
runBranchCreated,
|
|
13
|
+
testsWritten,
|
|
14
|
+
ticketBuilt,
|
|
15
|
+
ticketWorktreeCreated,
|
|
16
|
+
withInstalls,
|
|
17
|
+
worktreeReset,
|
|
18
|
+
} from '../testing/build-fixtures'
|
|
19
|
+
import { recordsFrom } from '../testing/intake-fixtures'
|
|
20
|
+
import { REFACTOR_LABEL } from '../tracker/tracker'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Helpers shared by the tests of the decision steps for building a ticket
|
|
24
|
+
* (`decide-build.test.ts` and the `decide-build-*.test.ts` files beside it).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export const TICKET = practiceTicket({ number: 11 })
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Decide on a run with one ticket (#11) and these entries after intake. Each
|
|
31
|
+
* new worktree gets a passing install unless the entries list one.
|
|
32
|
+
*/
|
|
33
|
+
export const decideAfter = (entries: JournalEntry[]) =>
|
|
34
|
+
decide({
|
|
35
|
+
records: recordsFrom({
|
|
36
|
+
entries: [
|
|
37
|
+
...intakePassed({ tickets: [TICKET] }),
|
|
38
|
+
...withInstalls({ entries }),
|
|
39
|
+
],
|
|
40
|
+
}),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/** The steps of ticket #11 up to and including the step at `index`. */
|
|
44
|
+
export const stepsUpTo = (index: number) => [
|
|
45
|
+
runBranchCreated(),
|
|
46
|
+
...ticketBuilt({ ticket: 11 }).slice(0, index + 1),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
/** Ticket #11 up to its first bad-test bounce and the worktree reset. */
|
|
50
|
+
export const bounced = (): JournalEntry[] => [
|
|
51
|
+
...stepsUpTo(5),
|
|
52
|
+
implemented({ ticket: 11, outcome: 'bad_test' }),
|
|
53
|
+
worktreeReset({ ticket: 11 }),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
/** After the bounce: fresh tests, a passing red check, the second red commit. */
|
|
57
|
+
export const replaced = (): JournalEntry[] => [
|
|
58
|
+
...bounced(),
|
|
59
|
+
testsWritten({ ticket: 11, session_id: 'tw-2' }),
|
|
60
|
+
redCheck({ ticket: 11, ok: true }),
|
|
61
|
+
leftoverScan({ ticket: 11, stage: 'red' }),
|
|
62
|
+
commitMade({ ticket: 11, stage: 'red' }),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
export const REFACTOR_TICKET = practiceTicket({
|
|
66
|
+
number: 11,
|
|
67
|
+
title: 'Split sum into its own file',
|
|
68
|
+
labels: ['ready-for-agent', REFACTOR_LABEL],
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
/** Decide on a run whose one ticket (#11) is a refactor ticket. */
|
|
72
|
+
export const decideRefactorAfter = (entries: JournalEntry[]) =>
|
|
73
|
+
decide({
|
|
74
|
+
records: recordsFrom({
|
|
75
|
+
entries: [
|
|
76
|
+
...intakePassed({ tickets: [REFACTOR_TICKET] }),
|
|
77
|
+
...withInstalls({
|
|
78
|
+
entries: [
|
|
79
|
+
runBranchCreated(),
|
|
80
|
+
ticketWorktreeCreated({ ticket: 11 }),
|
|
81
|
+
],
|
|
82
|
+
}),
|
|
83
|
+
baselineTests({ ticket: 11 }),
|
|
84
|
+
...entries,
|
|
85
|
+
],
|
|
86
|
+
}),
|
|
87
|
+
})
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Each ticket's next build step (`ticketSteps`): its worktree, the step its
|
|
3
|
+
* progress calls for, a skip, a crash, or its stuck steps, with the join queue
|
|
4
|
+
* deciding which approved ticket joins the run branch. A ticket with no step
|
|
5
|
+
* now because it waits on purpose (its `TicketWait`) waits for another
|
|
6
|
+
* ticket's shared baseline, the join queue, or the start queue. Pure.
|
|
7
|
+
*/
|
|
8
|
+
import sortBy from 'lodash/sortBy'
|
|
9
|
+
|
|
10
|
+
import { stuck, type BuildAction } from './build-action'
|
|
11
|
+
import type { BuildView } from './decide-build-view'
|
|
12
|
+
import { crashDetail, crashedOut } from './decide-crashes'
|
|
13
|
+
import { skipDependent } from './decide-stuck'
|
|
14
|
+
import {
|
|
15
|
+
baselineRunner,
|
|
16
|
+
nextTicketStep,
|
|
17
|
+
sharedBaselineStep,
|
|
18
|
+
} from './decide-ticket'
|
|
19
|
+
|
|
20
|
+
import type { RunState, TicketProgress } from '../journal/replay'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A ticket held on purpose between steps (#559): for `baseline`, another
|
|
24
|
+
* ticket's shared baseline tests; for `join`, the join queue; for `start`,
|
|
25
|
+
* the start queue; each on `on_ticket`.
|
|
26
|
+
*/
|
|
27
|
+
export type TicketWait = {
|
|
28
|
+
ticket: number
|
|
29
|
+
for: 'baseline' | 'join' | 'start'
|
|
30
|
+
on_ticket: number | null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A ticket's next steps, or the wait that holds it. */
|
|
34
|
+
type TicketPlan = { steps: BuildAction[]; wait: TicketWait | null }
|
|
35
|
+
|
|
36
|
+
const NOTHING: TicketPlan = { steps: [], wait: null }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether a ticket's review finally approved it (no fix round open) and it
|
|
40
|
+
* has not pushed yet: it waits in the join queue, or is joining.
|
|
41
|
+
*/
|
|
42
|
+
const inJoinQueue = (progress: TicketProgress): boolean =>
|
|
43
|
+
progress.approved_seq !== null &&
|
|
44
|
+
progress.review_fix === null &&
|
|
45
|
+
progress.pushed === null
|
|
46
|
+
|
|
47
|
+
/** Each ticket's plan: its next step, at most one, or what it waits for. */
|
|
48
|
+
const ticketPlans = ({
|
|
49
|
+
state,
|
|
50
|
+
view,
|
|
51
|
+
}: {
|
|
52
|
+
state: RunState
|
|
53
|
+
view: BuildView
|
|
54
|
+
}): TicketPlan[] => {
|
|
55
|
+
const {
|
|
56
|
+
spec_number,
|
|
57
|
+
snapshot,
|
|
58
|
+
run_branch,
|
|
59
|
+
base_branch,
|
|
60
|
+
run_notes,
|
|
61
|
+
numbers,
|
|
62
|
+
progress,
|
|
63
|
+
skipped,
|
|
64
|
+
finished,
|
|
65
|
+
isStuck,
|
|
66
|
+
stuckSteps,
|
|
67
|
+
} = view
|
|
68
|
+
|
|
69
|
+
// A ticket already on the run branch finishes its join first (#519): a
|
|
70
|
+
// retried ticket approved earlier never joins on top of it.
|
|
71
|
+
const queue = sortBy(
|
|
72
|
+
numbers.filter(
|
|
73
|
+
(number) =>
|
|
74
|
+
inJoinQueue(progress(number)) &&
|
|
75
|
+
!isStuck(number) &&
|
|
76
|
+
!skipped(number)
|
|
77
|
+
),
|
|
78
|
+
[
|
|
79
|
+
(number) => progress(number).joined === null,
|
|
80
|
+
(number) => progress(number).approved_seq,
|
|
81
|
+
]
|
|
82
|
+
)
|
|
83
|
+
// A stuck ticket's join is undone before any new ticket starts on it
|
|
84
|
+
// or joins on top of it (#519): a join on top would be undone with it.
|
|
85
|
+
const stuckJoinOnRunBranch = numbers.some(
|
|
86
|
+
(number) =>
|
|
87
|
+
isStuck(number) &&
|
|
88
|
+
progress(number).joined?.ok === true &&
|
|
89
|
+
progress(number).pushed === null
|
|
90
|
+
)
|
|
91
|
+
const joiner = stuckJoinOnRunBranch ? null : (queue[0] ?? null)
|
|
92
|
+
const can_move = (other: number) =>
|
|
93
|
+
!finished(other) && !skipped(other) && !isStuck(other)
|
|
94
|
+
|
|
95
|
+
return numbers.map((number): TicketPlan => {
|
|
96
|
+
const ticket = snapshot.tickets[number]
|
|
97
|
+
const ticketProgress = progress(number)
|
|
98
|
+
if (ticket === undefined || finished(number)) return NOTHING
|
|
99
|
+
if (skipped(number)) return NOTHING
|
|
100
|
+
// The same step cut off by crashes too often is not taken again.
|
|
101
|
+
const crash = crashedOut({
|
|
102
|
+
crashes: state.crashes,
|
|
103
|
+
keys: (key) => key === String(number),
|
|
104
|
+
})
|
|
105
|
+
if (crash !== null) {
|
|
106
|
+
return {
|
|
107
|
+
steps: [
|
|
108
|
+
stuck({
|
|
109
|
+
ticket: number,
|
|
110
|
+
reason: 'crashed',
|
|
111
|
+
detail: crashDetail({ crash }),
|
|
112
|
+
}),
|
|
113
|
+
],
|
|
114
|
+
wait: null,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (isStuck(number)) return { steps: stuckSteps(number), wait: null }
|
|
118
|
+
const skippedBlocker = ticket.blockers.find(skipped)
|
|
119
|
+
if (skippedBlocker !== undefined) {
|
|
120
|
+
return {
|
|
121
|
+
steps: [
|
|
122
|
+
skipDependent({
|
|
123
|
+
ticket: number,
|
|
124
|
+
because: skippedBlocker,
|
|
125
|
+
spec_number,
|
|
126
|
+
}),
|
|
127
|
+
],
|
|
128
|
+
wait: null,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (ticketProgress.worktree === null) {
|
|
132
|
+
const unblocked =
|
|
133
|
+
!stuckJoinOnRunBranch && ticket.blockers.every(finished)
|
|
134
|
+
if (unblocked && queue.length === 0) {
|
|
135
|
+
return {
|
|
136
|
+
steps: [
|
|
137
|
+
{
|
|
138
|
+
type: 'create_ticket_worktree',
|
|
139
|
+
ticket: number,
|
|
140
|
+
run_branch: run_branch.branch,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
wait: null,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Unblocked, it starts once the join queue is empty.
|
|
147
|
+
return unblocked
|
|
148
|
+
? {
|
|
149
|
+
steps: [],
|
|
150
|
+
wait: {
|
|
151
|
+
ticket: number,
|
|
152
|
+
for: 'start',
|
|
153
|
+
on_ticket: queue[0] ?? null,
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
: NOTHING
|
|
157
|
+
}
|
|
158
|
+
const baseline = { number, numbers, progress, can_move }
|
|
159
|
+
const step = nextTicketStep({
|
|
160
|
+
snapshot,
|
|
161
|
+
ticket,
|
|
162
|
+
progress: ticketProgress,
|
|
163
|
+
run_notes,
|
|
164
|
+
run_branch,
|
|
165
|
+
base_branch,
|
|
166
|
+
joiner: number === joiner,
|
|
167
|
+
setup_files: state.config?.test_setup_files ?? [],
|
|
168
|
+
baseline_step:
|
|
169
|
+
ticketProgress.baseline === null
|
|
170
|
+
? sharedBaselineStep(baseline)
|
|
171
|
+
: null,
|
|
172
|
+
})
|
|
173
|
+
if (step !== null) return { steps: [step], wait: null }
|
|
174
|
+
if (ticketProgress.baseline === null) {
|
|
175
|
+
return {
|
|
176
|
+
steps: [],
|
|
177
|
+
wait: {
|
|
178
|
+
ticket: number,
|
|
179
|
+
for: 'baseline',
|
|
180
|
+
on_ticket: baselineRunner(baseline),
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return inJoinQueue(ticketProgress) && number !== joiner
|
|
185
|
+
? {
|
|
186
|
+
steps: [],
|
|
187
|
+
wait: { ticket: number, for: 'join', on_ticket: joiner },
|
|
188
|
+
}
|
|
189
|
+
: NOTHING
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The next step of each ticket that can move now, at most one per ticket,
|
|
195
|
+
* and the tickets that wait on purpose between steps now, in ticket order:
|
|
196
|
+
* one decision, so a wait is only ever one that left its ticket no step.
|
|
197
|
+
*/
|
|
198
|
+
export const ticketSteps = (args: {
|
|
199
|
+
state: RunState
|
|
200
|
+
view: BuildView
|
|
201
|
+
}): { steps: BuildAction[]; waits: TicketWait[] } => {
|
|
202
|
+
const plans = ticketPlans(args)
|
|
203
|
+
return {
|
|
204
|
+
steps: plans.flatMap(({ steps }) => steps),
|
|
205
|
+
waits: plans.flatMap(({ wait }) => (wait === null ? [] : [wait])),
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the build half of the decision step reads about a run's tickets: the
|
|
3
|
+
* tickets in order, each one's progress, whether it is skipped, pushed,
|
|
4
|
+
* already done, finished, or stuck, and its stuck steps (`buildViewOf`). Pure.
|
|
5
|
+
*/
|
|
6
|
+
import type { BuildAction } from './build-action'
|
|
7
|
+
import { newestRunNotes } from './build-launch'
|
|
8
|
+
import { stuckTicketSteps } from './decide-stuck'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
EMPTY_TICKET_PROGRESS,
|
|
12
|
+
type ReplayedRunNote,
|
|
13
|
+
type ReplayedSnapshot,
|
|
14
|
+
type ReplayedWorktree,
|
|
15
|
+
type RunState,
|
|
16
|
+
type TicketProgress,
|
|
17
|
+
} from '../journal/replay'
|
|
18
|
+
|
|
19
|
+
/** The worktrees not removed yet, of these paths. */
|
|
20
|
+
export const notRemoved = ({
|
|
21
|
+
paths,
|
|
22
|
+
state,
|
|
23
|
+
}: {
|
|
24
|
+
paths: string[]
|
|
25
|
+
state: RunState
|
|
26
|
+
}): string[] => paths.filter((path) => !state.removed_worktrees.includes(path))
|
|
27
|
+
|
|
28
|
+
/** A run's tickets, as the build half of the decision step reads them. */
|
|
29
|
+
export type BuildView = {
|
|
30
|
+
spec_number: number
|
|
31
|
+
snapshot: ReplayedSnapshot
|
|
32
|
+
run_branch: ReplayedWorktree
|
|
33
|
+
base_branch: string
|
|
34
|
+
run_notes: ReplayedRunNote[]
|
|
35
|
+
/** The run's tickets that intake snapshotted, in order. */
|
|
36
|
+
numbers: number[]
|
|
37
|
+
progress: (number: number) => TicketProgress
|
|
38
|
+
skipped: (number: number) => boolean
|
|
39
|
+
pushed: (number: number) => boolean
|
|
40
|
+
alreadyDone: (number: number) => boolean
|
|
41
|
+
/** Pushed, or its work was already on the base branch (#484). */
|
|
42
|
+
finished: (number: number) => boolean
|
|
43
|
+
isStuck: (number: number) => boolean
|
|
44
|
+
/** The steps that tell the owner a ticket is stuck, and take its reply. */
|
|
45
|
+
stuckSteps: (number: number) => BuildAction[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The view of a run that has a run branch and a snapshot. */
|
|
49
|
+
export const buildViewOf = ({
|
|
50
|
+
state,
|
|
51
|
+
spec_number,
|
|
52
|
+
snapshot,
|
|
53
|
+
run_branch,
|
|
54
|
+
}: {
|
|
55
|
+
state: RunState
|
|
56
|
+
spec_number: number
|
|
57
|
+
snapshot: ReplayedSnapshot
|
|
58
|
+
run_branch: ReplayedWorktree
|
|
59
|
+
}): BuildView => {
|
|
60
|
+
const base_branch = state.base_branch ?? 'main'
|
|
61
|
+
const run_notes = newestRunNotes({ run_notes: state.run_notes })
|
|
62
|
+
const numbers = snapshot.ticket_order.filter(
|
|
63
|
+
(number) => snapshot.tickets[number] !== undefined
|
|
64
|
+
)
|
|
65
|
+
const progress = (number: number): TicketProgress =>
|
|
66
|
+
state.tickets[number] ?? EMPTY_TICKET_PROGRESS
|
|
67
|
+
|
|
68
|
+
const skipped = (number: number) => progress(number).skipped !== null
|
|
69
|
+
const pushed = (number: number) =>
|
|
70
|
+
!numbers.includes(number) || progress(number).pushed !== null
|
|
71
|
+
const alreadyDone = (number: number) =>
|
|
72
|
+
progress(number).already_done !== null
|
|
73
|
+
/** Pushed, or its work was already on the base branch (#484). */
|
|
74
|
+
const finished = (number: number) => pushed(number) || alreadyDone(number)
|
|
75
|
+
const stuckSteps = (number: number): BuildAction[] => {
|
|
76
|
+
const ticket = snapshot.tickets[number]
|
|
77
|
+
return ticket === undefined
|
|
78
|
+
? []
|
|
79
|
+
: stuckTicketSteps({
|
|
80
|
+
spec_number,
|
|
81
|
+
ticket,
|
|
82
|
+
progress: progress(number),
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
const isStuck = (number: number) =>
|
|
86
|
+
progress(number).stuck !== null && !skipped(number)
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
spec_number,
|
|
90
|
+
snapshot,
|
|
91
|
+
run_branch,
|
|
92
|
+
base_branch,
|
|
93
|
+
run_notes,
|
|
94
|
+
numbers,
|
|
95
|
+
progress,
|
|
96
|
+
skipped,
|
|
97
|
+
pushed,
|
|
98
|
+
alreadyDone,
|
|
99
|
+
finished,
|
|
100
|
+
isStuck,
|
|
101
|
+
stuckSteps,
|
|
102
|
+
}
|
|
103
|
+
}
|