@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,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run branch's joins, undone and redone: a join a crash cut off
|
|
3
|
+
* (`openJoin`), undoing a ticket's join, rebasing a ticket onto the run
|
|
4
|
+
* branch, and sending its tests back to the test-writer after a rebase.
|
|
5
|
+
*/
|
|
6
|
+
import uniq from 'lodash/uniq'
|
|
7
|
+
|
|
8
|
+
import { need, ticketWorktree, type BuildContext } from './build-context'
|
|
9
|
+
import type { BuildAction } from './decide-build'
|
|
10
|
+
import { installIn } from './execute-gates'
|
|
11
|
+
import { closeSessions, openSessionsIn } from './session-close'
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
dependenciesChanged,
|
|
15
|
+
rebaseNeedsInstall,
|
|
16
|
+
} from '../gates/lockfile-install'
|
|
17
|
+
import { testFilesAmong } from '../gates/test-runner'
|
|
18
|
+
import type { JournalRecord } from '../journal/journal-record'
|
|
19
|
+
import type { RunState } from '../journal/replay'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Where the run branch stood before a join of `ticket` a crash cut off: the
|
|
23
|
+
* latest `join_started` for it with no `ticket_joined` or `ticket_stuck`
|
|
24
|
+
* for it after, else `null`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* openJoin({ records: journal.read(), ticket: 11 }) // 'a1b2c3...' after a crash mid-join
|
|
28
|
+
*/
|
|
29
|
+
export const openJoin = ({
|
|
30
|
+
records,
|
|
31
|
+
ticket,
|
|
32
|
+
}: {
|
|
33
|
+
records: JournalRecord[]
|
|
34
|
+
ticket: number
|
|
35
|
+
}): string | null => {
|
|
36
|
+
const last = records.findLast(
|
|
37
|
+
(record) =>
|
|
38
|
+
record.ticket === ticket &&
|
|
39
|
+
(record.kind === 'join_started' ||
|
|
40
|
+
record.kind === 'ticket_joined' ||
|
|
41
|
+
record.kind === 'ticket_stuck')
|
|
42
|
+
)
|
|
43
|
+
return last?.kind === 'join_started' ? last.content.run_branch_sha : null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Puts the run branch back where it stood before a join of `ticket` a crash
|
|
48
|
+
* cut off, dropping whatever half of its commits the crash left there. A
|
|
49
|
+
* cut-off step is taken again before other steps start, so nothing else
|
|
50
|
+
* moved the run branch since. Does nothing when no join was cut off.
|
|
51
|
+
*/
|
|
52
|
+
export const undoCutOffJoin = async ({
|
|
53
|
+
context,
|
|
54
|
+
ticket,
|
|
55
|
+
}: {
|
|
56
|
+
context: BuildContext
|
|
57
|
+
ticket: number
|
|
58
|
+
}) => {
|
|
59
|
+
const sha = openJoin({ records: context.journal.read(), ticket })
|
|
60
|
+
if (sha === null) return
|
|
61
|
+
const runBranch = need({
|
|
62
|
+
value: context.state.run_branch,
|
|
63
|
+
what: 'run branch',
|
|
64
|
+
})
|
|
65
|
+
await context.git.resetWorktree({ cwd: runBranch.path, to: sha })
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The run branch commits an undo of `ticket`'s join may take (#519): its
|
|
70
|
+
* own joined commits, and those of any other ticket that joined and has not
|
|
71
|
+
* pushed (a later join on top goes with it, and replay clears that join
|
|
72
|
+
* too). A pushed ticket's commits never.
|
|
73
|
+
*/
|
|
74
|
+
const undoableCommits = ({
|
|
75
|
+
state,
|
|
76
|
+
ticket,
|
|
77
|
+
}: {
|
|
78
|
+
state: RunState
|
|
79
|
+
ticket: number
|
|
80
|
+
}): string[] =>
|
|
81
|
+
uniq(
|
|
82
|
+
Object.entries(state.tickets).flatMap(([number, progress]) => {
|
|
83
|
+
const { joined } = progress
|
|
84
|
+
if (!joined?.ok) return []
|
|
85
|
+
const own = Number(number) === ticket
|
|
86
|
+
return own || progress.pushed === null ? joined.shas : []
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Undoes a ticket's join on the run branch, back to before `first_sha`, and
|
|
92
|
+
* returns the commits undone. It never rewinds over other work (#519): it
|
|
93
|
+
* may only undo the ticket's own join and later joins not pushed yet
|
|
94
|
+
* (`undoableCommits`). When git refuses, the run branch is left alone, the
|
|
95
|
+
* ticket is stuck (`undo_refused`) with why, and it returns `null`, so the
|
|
96
|
+
* caller does nothing more. The undone joins' install is still in the run
|
|
97
|
+
* branch's `node_modules`, so when their commits changed dependency files
|
|
98
|
+
* the lockfile's install runs there again.
|
|
99
|
+
*/
|
|
100
|
+
export const undoJoin = async ({
|
|
101
|
+
context,
|
|
102
|
+
ticket,
|
|
103
|
+
first_sha,
|
|
104
|
+
}: {
|
|
105
|
+
context: BuildContext
|
|
106
|
+
ticket: number
|
|
107
|
+
first_sha: string
|
|
108
|
+
}): Promise<string[] | null> => {
|
|
109
|
+
const { git, state, journal, launcher } = context
|
|
110
|
+
const runBranch = need({ value: state.run_branch, what: 'run branch' })
|
|
111
|
+
const joined = state.tickets[ticket]?.joined
|
|
112
|
+
const tip = await git.head({ cwd: runBranch.path })
|
|
113
|
+
const undo = await git.undoReplay({
|
|
114
|
+
cwd: runBranch.path,
|
|
115
|
+
first_sha,
|
|
116
|
+
only: undoableCommits({ state, ticket }),
|
|
117
|
+
})
|
|
118
|
+
if (!undo.ok) {
|
|
119
|
+
// A stuck ticket's agents take no more follow-ups.
|
|
120
|
+
await closeSessions({
|
|
121
|
+
journal,
|
|
122
|
+
launcher,
|
|
123
|
+
sessions: openSessionsIn({ records: journal.read() }).filter(
|
|
124
|
+
(session) => session.ticket === ticket
|
|
125
|
+
),
|
|
126
|
+
})
|
|
127
|
+
journal.append({
|
|
128
|
+
kind: 'ticket_stuck',
|
|
129
|
+
ticket,
|
|
130
|
+
role: null,
|
|
131
|
+
content: {
|
|
132
|
+
reason: 'undo_refused',
|
|
133
|
+
detail: `The engine went to undo this ticket's join on the run branch (from ${first_sha.slice(0, 7)}), but that would have rewound the run branch over other work, so the run branch was left alone:\n${undo.error}`,
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
const { undone } = undo
|
|
139
|
+
const undoneFiles =
|
|
140
|
+
undone.length === 0
|
|
141
|
+
? []
|
|
142
|
+
: await git.filesBetween({
|
|
143
|
+
cwd: runBranch.path,
|
|
144
|
+
from: `${first_sha}^`,
|
|
145
|
+
to: tip,
|
|
146
|
+
})
|
|
147
|
+
// A redo whose first try undid the join before a crash finds nothing
|
|
148
|
+
// left to undo: the commits it undid are the journaled join's.
|
|
149
|
+
const named =
|
|
150
|
+
undone.length === 0 && context.step.redo && joined?.ok === true
|
|
151
|
+
? joined.shas
|
|
152
|
+
: undone
|
|
153
|
+
if (dependenciesChanged({ changed_files: undoneFiles })) {
|
|
154
|
+
await installIn({
|
|
155
|
+
journal,
|
|
156
|
+
cwd: runBranch.path,
|
|
157
|
+
target: 'run_branch',
|
|
158
|
+
ticket: null,
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
return named
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Puts a joined ticket's change back on top of the run branch: undoes the
|
|
166
|
+
* join first if asked (its gates failed), then resets the ticket's worktree
|
|
167
|
+
* to the run branch's tip and applies the ticket's whole diff there,
|
|
168
|
+
* uncommitted, with conflict markers in the files that clash. Journals the
|
|
169
|
+
* clashed files split into tests and code. A refused undo (#519) leaves the
|
|
170
|
+
* ticket stuck, and nothing is rebased.
|
|
171
|
+
*/
|
|
172
|
+
export const rebaseTicket = async ({
|
|
173
|
+
context,
|
|
174
|
+
action,
|
|
175
|
+
}: {
|
|
176
|
+
context: BuildContext
|
|
177
|
+
action: Extract<BuildAction, { type: 'rebase_ticket' }>
|
|
178
|
+
}) => {
|
|
179
|
+
const { git, state, journal, config } = context
|
|
180
|
+
const worktree = ticketWorktree({ state, ticket: action.ticket })
|
|
181
|
+
const runBranch = need({ value: state.run_branch, what: 'run branch' })
|
|
182
|
+
const progress = state.tickets[action.ticket]
|
|
183
|
+
// The ticket's latest commit: a review fix round's, or the green one.
|
|
184
|
+
const head = need({
|
|
185
|
+
value: progress?.commits.fix ?? progress?.commits.green,
|
|
186
|
+
what: `green commit for #${action.ticket}`,
|
|
187
|
+
})
|
|
188
|
+
const undone =
|
|
189
|
+
action.undo_first_sha === null
|
|
190
|
+
? []
|
|
191
|
+
: await undoJoin({
|
|
192
|
+
context,
|
|
193
|
+
ticket: action.ticket,
|
|
194
|
+
first_sha: action.undo_first_sha,
|
|
195
|
+
})
|
|
196
|
+
// A refused undo left the run branch alone and the ticket stuck.
|
|
197
|
+
if (undone === null) return
|
|
198
|
+
const onto = await git.head({ cwd: runBranch.path })
|
|
199
|
+
const moved_files = await git.filesBetween({
|
|
200
|
+
cwd: worktree.path,
|
|
201
|
+
from: worktree.base_sha,
|
|
202
|
+
to: onto,
|
|
203
|
+
})
|
|
204
|
+
const ticket_files = await git.filesBetween({
|
|
205
|
+
cwd: worktree.path,
|
|
206
|
+
from: worktree.base_sha,
|
|
207
|
+
to: head,
|
|
208
|
+
})
|
|
209
|
+
const { conflicts } = await git.rebaseWorktree({
|
|
210
|
+
cwd: worktree.path,
|
|
211
|
+
from: worktree.base_sha,
|
|
212
|
+
to: head,
|
|
213
|
+
onto,
|
|
214
|
+
})
|
|
215
|
+
const tests = testFilesAmong({
|
|
216
|
+
files: conflicts,
|
|
217
|
+
test_file_patterns: config.test_file_patterns,
|
|
218
|
+
})
|
|
219
|
+
journal.append({
|
|
220
|
+
kind: 'ticket_rebased',
|
|
221
|
+
ticket: action.ticket,
|
|
222
|
+
role: null,
|
|
223
|
+
content: {
|
|
224
|
+
cause: action.cause,
|
|
225
|
+
base_sha: onto,
|
|
226
|
+
tests,
|
|
227
|
+
code: conflicts.filter((file) => !tests.includes(file)),
|
|
228
|
+
undone,
|
|
229
|
+
reinstall: rebaseNeedsInstall({ moved_files, ticket_files }),
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Sends a ticket's tests back to its test-writer after a rebase (#489):
|
|
236
|
+
* looks up the tickets whose joined commits the run branch got between the
|
|
237
|
+
* ticket's old base and its new one, and the files the run branch changed
|
|
238
|
+
* there, then journals it all as `tests_sent_back`. Reads git only.
|
|
239
|
+
*/
|
|
240
|
+
export const sendTestsBack = async ({
|
|
241
|
+
context,
|
|
242
|
+
action,
|
|
243
|
+
}: {
|
|
244
|
+
context: BuildContext
|
|
245
|
+
action: Extract<BuildAction, { type: 'send_tests_back' }>
|
|
246
|
+
}) => {
|
|
247
|
+
const { git, state, journal } = context
|
|
248
|
+
const { ticket, round, bad_test, from_sha, base_sha } = action
|
|
249
|
+
const worktree = ticketWorktree({ state, ticket })
|
|
250
|
+
const commits = await git.commitsBetween({
|
|
251
|
+
cwd: worktree.path,
|
|
252
|
+
from: from_sha,
|
|
253
|
+
to: base_sha,
|
|
254
|
+
})
|
|
255
|
+
const files = await git.filesBetween({
|
|
256
|
+
cwd: worktree.path,
|
|
257
|
+
from: from_sha,
|
|
258
|
+
to: base_sha,
|
|
259
|
+
})
|
|
260
|
+
const joined = Object.entries(state.tickets).flatMap(
|
|
261
|
+
([number, progress]) => {
|
|
262
|
+
const other = Number(number)
|
|
263
|
+
const shas = progress.joined?.ok ? progress.joined.shas : []
|
|
264
|
+
if (other === ticket || !shas.some((sha) => commits.includes(sha)))
|
|
265
|
+
return []
|
|
266
|
+
const title = state.snapshot?.tickets[other]?.title ?? ''
|
|
267
|
+
return [{ ticket: other, title }]
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
journal.append({
|
|
271
|
+
kind: 'tests_sent_back',
|
|
272
|
+
ticket,
|
|
273
|
+
role: null,
|
|
274
|
+
content: { round, bad_test, from_sha, base_sha, joined, files },
|
|
275
|
+
})
|
|
276
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MemoryAction, MemorySaveStep } from './decide-memory'
|
|
2
2
|
import { buildContext, need, runTurn, type BuildDeps } from './execute-build'
|
|
3
3
|
|
|
4
|
+
import { launchPick } from '../jev/model-pick'
|
|
4
5
|
import type { Journal } from '../journal/journal'
|
|
5
6
|
import type { JournalRecord } from '../journal/journal-record'
|
|
6
7
|
import { replayRun } from '../journal/replay'
|
|
@@ -407,6 +408,14 @@ export const executeMemoryAction = async ({
|
|
|
407
408
|
value: built.state.spec_number,
|
|
408
409
|
what: 'spec',
|
|
409
410
|
})
|
|
411
|
+
const { model, model_reason } = launchPick({
|
|
412
|
+
records: journal.read(),
|
|
413
|
+
state: built.state,
|
|
414
|
+
config: built.config,
|
|
415
|
+
ticket: null,
|
|
416
|
+
role: 'learner',
|
|
417
|
+
step,
|
|
418
|
+
})
|
|
410
419
|
journal.append({
|
|
411
420
|
kind: 'agent_started',
|
|
412
421
|
ticket: null,
|
|
@@ -415,6 +424,8 @@ export const executeMemoryAction = async ({
|
|
|
415
424
|
role: 'learner',
|
|
416
425
|
prompt: action.prompt,
|
|
417
426
|
follow_up_of: null,
|
|
427
|
+
model,
|
|
428
|
+
model_reason,
|
|
418
429
|
},
|
|
419
430
|
})
|
|
420
431
|
await runTurn({
|
|
@@ -433,6 +444,7 @@ export const executeMemoryAction = async ({
|
|
|
433
444
|
config: built.config,
|
|
434
445
|
// The learner writes nothing and talks to nobody.
|
|
435
446
|
messaging: null,
|
|
447
|
+
model,
|
|
436
448
|
}),
|
|
437
449
|
})
|
|
438
450
|
return
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
|
|
3
|
+
import type { BuildAction } from './decide-build'
|
|
4
|
+
import type { BuildContext } from './execute-build'
|
|
5
|
+
import { openTicketsNotInRun } from './not-in-run'
|
|
6
|
+
import {
|
|
7
|
+
fitPullRequestBody,
|
|
8
|
+
PULL_REQUEST_FILE,
|
|
9
|
+
withNotInRun,
|
|
10
|
+
} from './pull-request-text'
|
|
11
|
+
|
|
12
|
+
import { postCommentOnce } from '../tracker/post-comment-once'
|
|
13
|
+
import { isTrackerFailure, type OpenedPullRequest } from '../tracker/tracker'
|
|
14
|
+
|
|
15
|
+
const errorText = (error: unknown): string =>
|
|
16
|
+
error instanceof Error ? error.message : String(error)
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Opens the run's one pull request, then journals `pull_request_opened`
|
|
20
|
+
* (#508). Its full text (with the spec's open tickets not in the run) is
|
|
21
|
+
* saved first to `pull-request.md` in the run folder, so it's never lost.
|
|
22
|
+
* An open PR from the run branch is reused, never opened twice: the one a
|
|
23
|
+
* try opened before a crash or a failure, or one the owner opened by hand.
|
|
24
|
+
* The body sent keeps under `PULL_REQUEST_BUDGET`, and what doesn't fit
|
|
25
|
+
* is posted as PR comments after it (`fitPullRequestBody`), each once. A
|
|
26
|
+
* failed tracker call (`isTrackerFailure`: finding or opening the PR, such
|
|
27
|
+
* as a failed `gh pr create`) journals the run stuck (`pull_request_failed`)
|
|
28
|
+
* with its error, for the owner's `retry`; any other error is a crash.
|
|
29
|
+
*/
|
|
30
|
+
export const openPullRequest = async ({
|
|
31
|
+
context,
|
|
32
|
+
action,
|
|
33
|
+
}: {
|
|
34
|
+
context: BuildContext
|
|
35
|
+
action: Extract<BuildAction, { type: 'open_pull_request' }>
|
|
36
|
+
}): Promise<void> => {
|
|
37
|
+
const { journal, tracker, state, run_dir, step } = context
|
|
38
|
+
const { head, base, title, body } = action
|
|
39
|
+
// The spec's open tickets not in the run are named in the PR.
|
|
40
|
+
const full = withNotInRun({
|
|
41
|
+
body,
|
|
42
|
+
open: await openTicketsNotInRun({ tracker, state }),
|
|
43
|
+
})
|
|
44
|
+
await Bun.write(join(run_dir, PULL_REQUEST_FILE), full)
|
|
45
|
+
const fitted = fitPullRequestBody({ body: full })
|
|
46
|
+
let opened: OpenedPullRequest
|
|
47
|
+
try {
|
|
48
|
+
opened =
|
|
49
|
+
(await tracker.findOpenPullRequest({ head })) ??
|
|
50
|
+
(await tracker.openPullRequest({
|
|
51
|
+
head,
|
|
52
|
+
base,
|
|
53
|
+
title,
|
|
54
|
+
body: fitted.body,
|
|
55
|
+
}))
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (!isTrackerFailure(error)) throw error
|
|
58
|
+
journal.append({
|
|
59
|
+
kind: 'run_stuck',
|
|
60
|
+
ticket: null,
|
|
61
|
+
role: null,
|
|
62
|
+
content: {
|
|
63
|
+
reason: 'pull_request_failed',
|
|
64
|
+
detail: errorText(error),
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
for (const [n, comment] of fitted.comments.entries()) {
|
|
70
|
+
await postCommentOnce({
|
|
71
|
+
tracker,
|
|
72
|
+
number: opened.number,
|
|
73
|
+
body: comment,
|
|
74
|
+
step,
|
|
75
|
+
n,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
journal.append({
|
|
79
|
+
kind: 'pull_request_opened',
|
|
80
|
+
ticket: null,
|
|
81
|
+
role: null,
|
|
82
|
+
content: { ...opened, head, base, title, body: full },
|
|
83
|
+
})
|
|
84
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `run_red_check` step: the test-writer's tests run in the ticket's
|
|
3
|
+
* worktree, checked against its baseline, its test map, and the feature
|
|
4
|
+
* map, and journaled as `red_check`.
|
|
5
|
+
*/
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
|
|
8
|
+
import uniq from 'lodash/uniq'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
need,
|
|
12
|
+
reportFile,
|
|
13
|
+
testFilesIn,
|
|
14
|
+
ticketWorktree,
|
|
15
|
+
type BuildContext,
|
|
16
|
+
} from './build-context'
|
|
17
|
+
import type { BuildAction } from './decide-build'
|
|
18
|
+
import { leaveOutIn, ticketChanges, watchPrepare } from './worktree-changes'
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
bunTestCommands,
|
|
22
|
+
featureMapPath,
|
|
23
|
+
hasSlowTests,
|
|
24
|
+
testTimeoutOf,
|
|
25
|
+
} from '../config/engine-config'
|
|
26
|
+
import { readFeatureMap } from '../gates/feature-map'
|
|
27
|
+
import { prepareCheck } from '../gates/gate-runner'
|
|
28
|
+
import { endedText } from '../gates/gate-schemas'
|
|
29
|
+
import { checkRed, type ChangedTestSource } from '../gates/red-check'
|
|
30
|
+
import {
|
|
31
|
+
fileTestsCommand,
|
|
32
|
+
runBunTests,
|
|
33
|
+
runTests,
|
|
34
|
+
testFilesAmong,
|
|
35
|
+
withOwnRun,
|
|
36
|
+
} from '../gates/test-runner'
|
|
37
|
+
import { pathText } from '../git/path-text'
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The red check's problem when the test-writer changed no file (what
|
|
41
|
+
* prepare made aside): the red commit would have nothing to commit.
|
|
42
|
+
*/
|
|
43
|
+
export const NO_RED_CHANGES =
|
|
44
|
+
'No file changed, so the red commit would be empty: write the tests for the criteria in the worktree (new or changed test files).'
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The repo's feature map as the red check needs it (#583), read from the
|
|
48
|
+
* worktree at `cwd`: its configured path (a file or a folder of pages,
|
|
49
|
+
* #597), and the test files it lists. `files` is `null` when the map is
|
|
50
|
+
* missing or can't be read, so the red check can name it rather than crash.
|
|
51
|
+
* `null` when the config names no map.
|
|
52
|
+
*/
|
|
53
|
+
const featureMapIn = async ({
|
|
54
|
+
context,
|
|
55
|
+
cwd,
|
|
56
|
+
}: {
|
|
57
|
+
context: BuildContext
|
|
58
|
+
cwd: string
|
|
59
|
+
}): Promise<{ path: string; files: string[] | null } | null> => {
|
|
60
|
+
const path = featureMapPath({ config: context.config })
|
|
61
|
+
if (path === null) return null
|
|
62
|
+
return {
|
|
63
|
+
path,
|
|
64
|
+
files: await readFeatureMap({
|
|
65
|
+
cwd,
|
|
66
|
+
path,
|
|
67
|
+
test_file_patterns: context.config.test_file_patterns,
|
|
68
|
+
}),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const runRedCheck = async ({
|
|
73
|
+
context,
|
|
74
|
+
action,
|
|
75
|
+
}: {
|
|
76
|
+
context: BuildContext
|
|
77
|
+
action: Extract<BuildAction, { type: 'run_red_check' }>
|
|
78
|
+
}) => {
|
|
79
|
+
const { path, base_sha } = ticketWorktree({
|
|
80
|
+
state: context.state,
|
|
81
|
+
ticket: action.ticket,
|
|
82
|
+
})
|
|
83
|
+
const baseline = need({
|
|
84
|
+
value: context.state.tickets[action.ticket]?.baseline,
|
|
85
|
+
what: `baseline test run for #${action.ticket}`,
|
|
86
|
+
})
|
|
87
|
+
const test_files = await testFilesIn({ context, cwd: path })
|
|
88
|
+
// Before prepare runs, so what it makes now can't count as a change.
|
|
89
|
+
const leave_out = await leaveOutIn({
|
|
90
|
+
context,
|
|
91
|
+
cwd: path,
|
|
92
|
+
ticket: action.ticket,
|
|
93
|
+
})
|
|
94
|
+
const changed = await ticketChanges({ context, cwd: path, leave_out })
|
|
95
|
+
// Every test file that differs from the ticket's base, not only from
|
|
96
|
+
// HEAD: after a bad-test bounce, HEAD may still hold an old red commit,
|
|
97
|
+
// whose test files the fresh test-writer may not touch (#581).
|
|
98
|
+
const base_changed = testFilesAmong({
|
|
99
|
+
files: (
|
|
100
|
+
await context.git.changedSince({ cwd: path, from: base_sha })
|
|
101
|
+
).filter((file) => !leave_out.includes(file)),
|
|
102
|
+
test_file_patterns: context.config.test_file_patterns,
|
|
103
|
+
})
|
|
104
|
+
const prepare = await watchPrepare({
|
|
105
|
+
context,
|
|
106
|
+
cwd: path,
|
|
107
|
+
ticket: action.ticket,
|
|
108
|
+
run: () => prepareCheck({ config: context.config, cwd: path }),
|
|
109
|
+
})
|
|
110
|
+
const files = uniq(
|
|
111
|
+
action.mapping.flatMap(({ tests }) => tests.map(({ file }) => file))
|
|
112
|
+
)
|
|
113
|
+
// A lone rerun (#544) runs each test command alone.
|
|
114
|
+
const exclusive = action.exclusive === true
|
|
115
|
+
const fast = await runBunTests({
|
|
116
|
+
cwd: path,
|
|
117
|
+
commands: bunTestCommands(context),
|
|
118
|
+
test_files,
|
|
119
|
+
timeout_ms: testTimeoutOf({ config: context.config }),
|
|
120
|
+
test_concurrency: context.config.test_concurrency,
|
|
121
|
+
exclusive,
|
|
122
|
+
report_file: await reportFile({
|
|
123
|
+
context,
|
|
124
|
+
ticket: action.ticket,
|
|
125
|
+
label: 'red',
|
|
126
|
+
}),
|
|
127
|
+
})
|
|
128
|
+
// With a slow command, the fast tests leave out every slow test file
|
|
129
|
+
// (#564): the files the test map names or the ticket changed, that the
|
|
130
|
+
// fast run gave no results for, run on their own, with plain `bun test`.
|
|
131
|
+
// A run that gives no results at all leaves the fast run as it is (see
|
|
132
|
+
// `withOwnRun`): its files don't load, as the fast run said already.
|
|
133
|
+
const own = hasSlowTests({ config: context.config })
|
|
134
|
+
? uniq([
|
|
135
|
+
...files,
|
|
136
|
+
...testFilesAmong({
|
|
137
|
+
files: changed.map((change) => change.path),
|
|
138
|
+
test_file_patterns: context.config.test_file_patterns,
|
|
139
|
+
}),
|
|
140
|
+
]).filter((file) => fast.files_without_results.includes(file))
|
|
141
|
+
: []
|
|
142
|
+
const current =
|
|
143
|
+
own.length === 0
|
|
144
|
+
? fast
|
|
145
|
+
: withOwnRun({
|
|
146
|
+
fast,
|
|
147
|
+
own: await runTests({
|
|
148
|
+
cwd: path,
|
|
149
|
+
command: fileTestsCommand({ files: own }),
|
|
150
|
+
test_files: own,
|
|
151
|
+
report_file: await reportFile({
|
|
152
|
+
context,
|
|
153
|
+
ticket: action.ticket,
|
|
154
|
+
label: 'red-own',
|
|
155
|
+
}),
|
|
156
|
+
timeout_ms: testTimeoutOf({ config: context.config }),
|
|
157
|
+
test_concurrency: context.config.test_concurrency,
|
|
158
|
+
exclusive,
|
|
159
|
+
}),
|
|
160
|
+
test_files,
|
|
161
|
+
})
|
|
162
|
+
const sources: Record<string, string | null> = {}
|
|
163
|
+
for (const file of files) {
|
|
164
|
+
sources[file] = await pathText({ path: join(path, file) })
|
|
165
|
+
}
|
|
166
|
+
// Each of those test files as it was at the ticket's base and as it is
|
|
167
|
+
// now, so the red check can find each new test (#581).
|
|
168
|
+
const changed_test_sources: ChangedTestSource[] = []
|
|
169
|
+
for (const file of base_changed) {
|
|
170
|
+
changed_test_sources.push({
|
|
171
|
+
file,
|
|
172
|
+
base: await context.git.fileAt({
|
|
173
|
+
cwd: path,
|
|
174
|
+
sha: base_sha,
|
|
175
|
+
path: file,
|
|
176
|
+
}),
|
|
177
|
+
current: await pathText({ path: join(path, file) }),
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
const checked = checkRed({
|
|
181
|
+
criteria_ids: action.criteria_ids,
|
|
182
|
+
mapping: action.mapping,
|
|
183
|
+
baseline,
|
|
184
|
+
current,
|
|
185
|
+
test_files,
|
|
186
|
+
sources,
|
|
187
|
+
changed_test_sources,
|
|
188
|
+
feature_map: await featureMapIn({ context, cwd: path }),
|
|
189
|
+
})
|
|
190
|
+
// A failed prepare after the test-writer's turn is one more problem,
|
|
191
|
+
// for the test-writer's fix loop; so is a turn that changed no file,
|
|
192
|
+
// which would leave the red commit nothing to commit (#494).
|
|
193
|
+
const problems = [
|
|
194
|
+
...(prepare === null || prepare.ok
|
|
195
|
+
? []
|
|
196
|
+
: [
|
|
197
|
+
`The prepare command \`${prepare.command}\` failed (${endedText(prepare)}):\n${prepare.output}`,
|
|
198
|
+
]),
|
|
199
|
+
...(changed.length > 0 ? [] : [NO_RED_CHANGES]),
|
|
200
|
+
]
|
|
201
|
+
const result =
|
|
202
|
+
problems.length === 0
|
|
203
|
+
? checked
|
|
204
|
+
: {
|
|
205
|
+
...checked,
|
|
206
|
+
ok: false,
|
|
207
|
+
problems: [...problems, ...checked.problems],
|
|
208
|
+
}
|
|
209
|
+
context.journal.append({
|
|
210
|
+
kind: 'red_check',
|
|
211
|
+
ticket: action.ticket,
|
|
212
|
+
role: null,
|
|
213
|
+
content: {
|
|
214
|
+
...result,
|
|
215
|
+
tests: current,
|
|
216
|
+
...(exclusive ? { exclusive } : {}),
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
}
|