@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,48 @@
|
|
|
1
|
+
import { mkdir, symlink } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { $ } from 'bun'
|
|
5
|
+
|
|
6
|
+
/** The folders a skill helper can be started from, for one repo and none. */
|
|
7
|
+
export type Checkouts = {
|
|
8
|
+
/** The main checkout: where a run's `run_started` repo points. */
|
|
9
|
+
main: string
|
|
10
|
+
/** A `git worktree add` of the main checkout. */
|
|
11
|
+
worktree: string
|
|
12
|
+
/** A folder inside the main checkout. */
|
|
13
|
+
subfolder: string
|
|
14
|
+
/** A symlink to the main checkout. */
|
|
15
|
+
link: string
|
|
16
|
+
/** A folder in no git repo. */
|
|
17
|
+
outside: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Real throwaway checkouts under `dir`: a git repo with one commit, a
|
|
22
|
+
* worktree of it, a subfolder, a symlink to it, and a folder outside git.
|
|
23
|
+
* For the `/luca-unstick` and `/luca-retro` helpers' repo lookup, which must
|
|
24
|
+
* find the same runs from each of them.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const { main, worktree } = await makeCheckouts({ dir })
|
|
28
|
+
*/
|
|
29
|
+
export const makeCheckouts = async ({
|
|
30
|
+
dir,
|
|
31
|
+
}: {
|
|
32
|
+
dir: string
|
|
33
|
+
}): Promise<Checkouts> => {
|
|
34
|
+
const main = join(dir, 'app')
|
|
35
|
+
const worktree = join(dir, 'app-worktree')
|
|
36
|
+
const subfolder = join(main, 'src', 'deep')
|
|
37
|
+
const link = join(dir, 'app-link')
|
|
38
|
+
const outside = join(dir, 'not-git')
|
|
39
|
+
await mkdir(subfolder, { recursive: true })
|
|
40
|
+
await mkdir(outside, { recursive: true })
|
|
41
|
+
await Bun.write(join(subfolder, 'a.txt'), 'a\n')
|
|
42
|
+
await $`git init -q -b main && git add -A && git -c user.name=t -c user.email=t@t -c commit.gpgsign=false commit -qm init`
|
|
43
|
+
.cwd(main)
|
|
44
|
+
.quiet()
|
|
45
|
+
await $`git worktree add -q -b side ${worktree}`.cwd(main).quiet()
|
|
46
|
+
await symlink(main, link)
|
|
47
|
+
return { main, worktree, subfolder, link, outside }
|
|
48
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type Finding,
|
|
5
5
|
type FindingRuling,
|
|
6
6
|
type LensName,
|
|
7
|
+
type MergeDanger,
|
|
7
8
|
} from '../agents/role-results'
|
|
8
9
|
import type { JournalEntry, StuckReason } from '../journal/journal-record'
|
|
9
10
|
|
|
@@ -22,6 +23,8 @@ export const finalReviewStarted = ({
|
|
|
22
23
|
lenses,
|
|
23
24
|
files,
|
|
24
25
|
rules,
|
|
26
|
+
slow_due,
|
|
27
|
+
fresh,
|
|
25
28
|
}: {
|
|
26
29
|
round: number
|
|
27
30
|
/** Defaults to `b0`, where the fixtures' run branch starts. */
|
|
@@ -33,6 +36,16 @@ export const finalReviewStarted = ({
|
|
|
33
36
|
/** Defaults to `src/sum.ts` and its test. */
|
|
34
37
|
files?: string[]
|
|
35
38
|
rules?: { path: string; text: string | null }[]
|
|
39
|
+
/**
|
|
40
|
+
* Round 1's slow tests are due (#563). Left out, the record has no
|
|
41
|
+
* `slow_due`, as an older engine's has none.
|
|
42
|
+
*/
|
|
43
|
+
slow_due?: boolean
|
|
44
|
+
/**
|
|
45
|
+
* The final review's fresh pass (#548). Left out, the record has no
|
|
46
|
+
* `fresh`, as an older engine's has none.
|
|
47
|
+
*/
|
|
48
|
+
fresh?: boolean
|
|
36
49
|
}): JournalEntry => ({
|
|
37
50
|
kind: 'final_review_started',
|
|
38
51
|
ticket: null,
|
|
@@ -44,6 +57,8 @@ export const finalReviewStarted = ({
|
|
|
44
57
|
lenses: lenses ?? [...LENS_NAMES],
|
|
45
58
|
files: files ?? ['src/sum.test.ts', 'src/sum.ts'],
|
|
46
59
|
rules: rules ?? [],
|
|
60
|
+
...(slow_due === undefined ? {} : { slow_due }),
|
|
61
|
+
...(fresh === undefined ? {} : { fresh }),
|
|
47
62
|
},
|
|
48
63
|
})
|
|
49
64
|
|
|
@@ -79,12 +94,18 @@ export const lensReviewed = ({
|
|
|
79
94
|
findings,
|
|
80
95
|
rulings,
|
|
81
96
|
assumptions,
|
|
97
|
+
summary_picture,
|
|
98
|
+
merge_danger,
|
|
82
99
|
}: {
|
|
83
100
|
lens: LensName
|
|
84
101
|
round: number
|
|
85
102
|
findings?: Finding[]
|
|
86
103
|
rulings?: FindingRuling[]
|
|
87
104
|
assumptions?: string[]
|
|
105
|
+
/** The integration lens's Summary picture (#513); left out by default. */
|
|
106
|
+
summary_picture?: string | null
|
|
107
|
+
/** The integration lens's Merge Danger (#513); left out by default. */
|
|
108
|
+
merge_danger?: MergeDanger | null
|
|
88
109
|
}): JournalEntry[] => {
|
|
89
110
|
const list = findings ?? []
|
|
90
111
|
const role = lensRole({ lens })
|
|
@@ -118,6 +139,10 @@ export const lensReviewed = ({
|
|
|
118
139
|
rulings: rulings ?? [],
|
|
119
140
|
summary: `The ${lens} lens looked.`,
|
|
120
141
|
assumptions: assumptions ?? [],
|
|
142
|
+
...(summary_picture === undefined
|
|
143
|
+
? {}
|
|
144
|
+
: { summary_picture }),
|
|
145
|
+
...(merge_danger === undefined ? {} : { merge_danger }),
|
|
121
146
|
},
|
|
122
147
|
},
|
|
123
148
|
},
|
|
@@ -148,6 +173,8 @@ export const lensRound = ({
|
|
|
148
173
|
findings,
|
|
149
174
|
from_sha,
|
|
150
175
|
head_sha,
|
|
176
|
+
slow_due,
|
|
177
|
+
fresh,
|
|
151
178
|
}: {
|
|
152
179
|
round: number
|
|
153
180
|
/** Defaults to every lens. */
|
|
@@ -155,16 +182,50 @@ export const lensRound = ({
|
|
|
155
182
|
findings?: Partial<Record<LensName, Finding[]>>
|
|
156
183
|
from_sha?: string
|
|
157
184
|
head_sha?: string
|
|
185
|
+
/** The round's slow tests are due (#563); see `finalReviewStarted`. */
|
|
186
|
+
slow_due?: boolean
|
|
187
|
+
/** The fresh pass (#548); see `finalReviewStarted`. */
|
|
188
|
+
fresh?: boolean
|
|
158
189
|
}): JournalEntry[] => {
|
|
159
190
|
const due = lenses ?? [...LENS_NAMES]
|
|
160
191
|
return [
|
|
161
|
-
finalReviewStarted({
|
|
192
|
+
finalReviewStarted({
|
|
193
|
+
round,
|
|
194
|
+
lenses: due,
|
|
195
|
+
from_sha,
|
|
196
|
+
head_sha,
|
|
197
|
+
slow_due,
|
|
198
|
+
fresh,
|
|
199
|
+
}),
|
|
162
200
|
...due.flatMap((lens) =>
|
|
163
201
|
lensReviewed({ lens, round, findings: findings?.[lens] })
|
|
164
202
|
),
|
|
165
203
|
]
|
|
166
204
|
}
|
|
167
205
|
|
|
206
|
+
/**
|
|
207
|
+
* The final review's slow tests on the run branch (#563): `bun test
|
|
208
|
+
* .e2e.test`, passing or failing.
|
|
209
|
+
*/
|
|
210
|
+
export const slowTestsRun = ({ ok }: { ok: boolean }): JournalEntry => ({
|
|
211
|
+
kind: 'gates_run',
|
|
212
|
+
ticket: null,
|
|
213
|
+
role: null,
|
|
214
|
+
content: {
|
|
215
|
+
target: 'final_slow',
|
|
216
|
+
ok,
|
|
217
|
+
checks: [
|
|
218
|
+
{
|
|
219
|
+
name: 'slow_test',
|
|
220
|
+
command: 'bun test .e2e.test',
|
|
221
|
+
ok,
|
|
222
|
+
exit_code: ok ? 0 : 1,
|
|
223
|
+
output: ok ? '' : '1 fail',
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
|
|
168
229
|
/** A final review fix round starts. */
|
|
169
230
|
export const finalReviewFixing = ({
|
|
170
231
|
round,
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commitMade,
|
|
3
|
+
gatesRun,
|
|
4
|
+
implemented,
|
|
5
|
+
intakePassed,
|
|
6
|
+
leftoverScan,
|
|
7
|
+
practiceTicket,
|
|
8
|
+
pushed,
|
|
9
|
+
runBranchCreated,
|
|
10
|
+
testsWritten,
|
|
11
|
+
ticketBuilt,
|
|
12
|
+
withInstalls,
|
|
13
|
+
} from './build-fixtures'
|
|
14
|
+
import {
|
|
15
|
+
finalReviewFixing,
|
|
16
|
+
lensFinding,
|
|
17
|
+
lensRound,
|
|
18
|
+
} from './final-review-fixtures'
|
|
19
|
+
import { recordsFrom } from './intake-fixtures'
|
|
20
|
+
|
|
21
|
+
import { decideSteps, type EngineAction } from '../core/decide'
|
|
22
|
+
import type { JournalEntry } from '../journal/journal-record'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The final review's decision-step tests' shared journal: practice ticket
|
|
26
|
+
* #11 built and pushed, its lens findings, and its fix rounds, with the
|
|
27
|
+
* steps the decision step takes after them.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export const TICKET = practiceTicket({ number: 11 })
|
|
31
|
+
|
|
32
|
+
/** Every step the decision step can take now, after these entries. */
|
|
33
|
+
export const stepsAfter = (
|
|
34
|
+
entries: JournalEntry[],
|
|
35
|
+
tickets = [TICKET]
|
|
36
|
+
): EngineAction[] =>
|
|
37
|
+
decideSteps({
|
|
38
|
+
records: recordsFrom({
|
|
39
|
+
entries: [
|
|
40
|
+
...intakePassed({ tickets }),
|
|
41
|
+
...withInstalls({ entries }),
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
/** The one step the decision step takes after these entries. */
|
|
47
|
+
export const stepAfter = (entries: JournalEntry[]): EngineAction => {
|
|
48
|
+
const steps = stepsAfter(entries)
|
|
49
|
+
if (steps.length !== 1) {
|
|
50
|
+
throw new Error(steps.map(({ type }) => type).join(', '))
|
|
51
|
+
}
|
|
52
|
+
return steps[0] as EngineAction
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The run branch with ticket #11 built, joined, and pushed. */
|
|
56
|
+
export const built = (): JournalEntry[] => [
|
|
57
|
+
runBranchCreated(),
|
|
58
|
+
...ticketBuilt({ ticket: 11 }),
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
/** The prompt of the `launch_lens` step for this lens. */
|
|
62
|
+
export const lensPrompt = (steps: EngineAction[], lens: string): string => {
|
|
63
|
+
const step = steps.find(
|
|
64
|
+
(entry) => entry.type === 'launch_lens' && entry.lens === lens
|
|
65
|
+
)
|
|
66
|
+
if (step?.type !== 'launch_lens') throw new Error(`no ${lens} lens`)
|
|
67
|
+
return step.prompt
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const SECURITY = lensFinding({ id: 'S1', title: 'Sum trusts its input' })
|
|
71
|
+
export const ARCH_TEST = lensFinding({
|
|
72
|
+
id: 'A1',
|
|
73
|
+
kind: 'test',
|
|
74
|
+
severity: 'blocker',
|
|
75
|
+
title: 'The tests reach into private helpers',
|
|
76
|
+
file: 'src/sum.test.ts',
|
|
77
|
+
})
|
|
78
|
+
export const NIT = lensFinding({
|
|
79
|
+
id: 'N1',
|
|
80
|
+
severity: 'nit',
|
|
81
|
+
title: 'Shorter name',
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
/** Round 1 with a code finding (security), a test finding (architecture), and a nit. */
|
|
85
|
+
export const findingsRound = (): JournalEntry[] =>
|
|
86
|
+
lensRound({
|
|
87
|
+
round: 1,
|
|
88
|
+
findings: {
|
|
89
|
+
security: [SECURITY],
|
|
90
|
+
architecture: [ARCH_TEST],
|
|
91
|
+
simplification: [NIT],
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
/** Fix round 1 answered: the test-writer, then a fresh implementer. */
|
|
96
|
+
export const fixersAnswered = ({
|
|
97
|
+
security,
|
|
98
|
+
}: {
|
|
99
|
+
security: 'fixed' | 'wont_fix'
|
|
100
|
+
}): JournalEntry[] => [
|
|
101
|
+
finalReviewFixing({ round: 1 }),
|
|
102
|
+
testsWritten({
|
|
103
|
+
ticket: null,
|
|
104
|
+
finding_responses: [
|
|
105
|
+
{ finding_id: 'architecture-A1', response: 'fixed', reason: '' },
|
|
106
|
+
],
|
|
107
|
+
}),
|
|
108
|
+
implemented({
|
|
109
|
+
ticket: null,
|
|
110
|
+
finding_responses: [
|
|
111
|
+
{
|
|
112
|
+
finding_id: 'security-S1',
|
|
113
|
+
response: security,
|
|
114
|
+
reason: security === 'fixed' ? '' : 'Inputs are typed numbers.',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
}),
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
/** The fixes pass the gates, are committed as `sha`, and pushed. */
|
|
121
|
+
export const fixesLanded = ({ sha }: { sha: string }): JournalEntry[] => [
|
|
122
|
+
gatesRun({ ticket: null, target: 'run_branch', ok: true }),
|
|
123
|
+
leftoverScan({ ticket: null, stage: 'fix' }),
|
|
124
|
+
commitMade({ ticket: null, stage: 'fix', sha, files: ['src/sum.ts'] }),
|
|
125
|
+
pushed({ ticket: null, sha }),
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
/** A whole fix round of the security finding, up to its push. */
|
|
129
|
+
export const securityFixRound = ({
|
|
130
|
+
round,
|
|
131
|
+
}: {
|
|
132
|
+
round: number
|
|
133
|
+
}): JournalEntry[] => [
|
|
134
|
+
finalReviewFixing({ round }),
|
|
135
|
+
implemented({
|
|
136
|
+
ticket: null,
|
|
137
|
+
session_id: `impl-${round}`,
|
|
138
|
+
finding_responses: [
|
|
139
|
+
{ finding_id: 'security-S1', response: 'fixed', reason: '' },
|
|
140
|
+
],
|
|
141
|
+
}),
|
|
142
|
+
...fixesLanded({ sha: `fix-${round}` }),
|
|
143
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal entry builders for plan limits: a `rate_limit_event`'s reading as
|
|
3
|
+
* the SDK sends it, a limit wait started and ended, and a billing stop.
|
|
4
|
+
*/
|
|
5
|
+
import type { JournalEntry } from '../journal/journal-record'
|
|
6
|
+
|
|
7
|
+
/** Seconds since the epoch for an ISO time, as `resetsAt` gives it. */
|
|
8
|
+
export const epochSeconds = (iso: string): number =>
|
|
9
|
+
Math.floor(Date.parse(iso) / 1000)
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One `rate_limit_event`'s info as the SDK sends it, with the windows'
|
|
13
|
+
* utilization under `unifiedWindows` as real readings have it.
|
|
14
|
+
*/
|
|
15
|
+
export const rateLimitReading = ({
|
|
16
|
+
status,
|
|
17
|
+
rate_limit_type,
|
|
18
|
+
resets_at,
|
|
19
|
+
is_using_overage,
|
|
20
|
+
windows,
|
|
21
|
+
}: {
|
|
22
|
+
status: 'allowed' | 'allowed_warning' | 'rejected'
|
|
23
|
+
/** Defaults to `five_hour`. */
|
|
24
|
+
rate_limit_type?: string
|
|
25
|
+
/** An ISO time; leave it out for a reading with no `resetsAt`. */
|
|
26
|
+
resets_at?: string
|
|
27
|
+
is_using_overage?: boolean
|
|
28
|
+
/** Utilization (0 to 1) per window, such as `{ five_hour: 0.2 }`. */
|
|
29
|
+
windows?: Record<string, number>
|
|
30
|
+
}): Record<string, unknown> => ({
|
|
31
|
+
status,
|
|
32
|
+
rateLimitType: rate_limit_type ?? 'five_hour',
|
|
33
|
+
...(resets_at === undefined ? {} : { resetsAt: epochSeconds(resets_at) }),
|
|
34
|
+
overageStatus: 'rejected',
|
|
35
|
+
overageDisabledReason: 'org_level_disabled',
|
|
36
|
+
isUsingOverage: is_using_overage ?? false,
|
|
37
|
+
unifiedWindows: Object.fromEntries(
|
|
38
|
+
Object.entries(windows ?? {}).map(([name, utilization]) => [
|
|
39
|
+
name,
|
|
40
|
+
{ utilization },
|
|
41
|
+
])
|
|
42
|
+
),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/** A limit wait started, hit by #11's test-writer unless told otherwise. */
|
|
46
|
+
export const limitWaitStarted = ({
|
|
47
|
+
until,
|
|
48
|
+
resets_at,
|
|
49
|
+
rate_limit_type,
|
|
50
|
+
}: {
|
|
51
|
+
until: string
|
|
52
|
+
resets_at: string | null
|
|
53
|
+
/** Defaults to `five_hour`. */
|
|
54
|
+
rate_limit_type?: string
|
|
55
|
+
}): JournalEntry => ({
|
|
56
|
+
kind: 'limit_wait_started',
|
|
57
|
+
ticket: null,
|
|
58
|
+
role: null,
|
|
59
|
+
content: {
|
|
60
|
+
until,
|
|
61
|
+
resets_at,
|
|
62
|
+
rate_limit_type: rate_limit_type ?? 'five_hour',
|
|
63
|
+
hit_ticket: 11,
|
|
64
|
+
hit_role: 'test-writer',
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
export const limitWaitEnded = ({ until }: { until: string }): JournalEntry => ({
|
|
69
|
+
kind: 'limit_wait_ended',
|
|
70
|
+
ticket: null,
|
|
71
|
+
role: null,
|
|
72
|
+
content: { until },
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
/** The run stopped for good on a sign of per-token billing. */
|
|
76
|
+
export const billingStopped = ({
|
|
77
|
+
reason,
|
|
78
|
+
}: {
|
|
79
|
+
reason: string
|
|
80
|
+
}): JournalEntry => ({
|
|
81
|
+
kind: 'run_stopped',
|
|
82
|
+
ticket: null,
|
|
83
|
+
role: null,
|
|
84
|
+
content: { reason, role: null, billing: true },
|
|
85
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { LiveFileSchema, type LiveFile } from '../core/live-file'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The run's live file (#628) as the engine's tests read it: `live.json`
|
|
8
|
+
* parsed with the writer's own schema (`core/live-file.ts`), never cast, so
|
|
9
|
+
* a field the writer renames or drops fails the test that reads it.
|
|
10
|
+
* The board reads the same file with its own, more forgiving schemas
|
|
11
|
+
* (`packages/board/server/live-file.ts`).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
LiveFileSchema,
|
|
16
|
+
type LiveAgent,
|
|
17
|
+
type LiveFile,
|
|
18
|
+
type LiveHelper,
|
|
19
|
+
type LiveWait,
|
|
20
|
+
} from '../core/live-file'
|
|
21
|
+
|
|
22
|
+
/** The live file of the practice run (`run-1`) in `dir`. */
|
|
23
|
+
export const liveFileIn = (dir: string): string =>
|
|
24
|
+
join(dir, 'runs', 'run-1', 'live.json')
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The live file at `file`, or `null` while there is none. A file that is
|
|
28
|
+
* there but doesn't fit `LiveFileSchema` throws, naming the field.
|
|
29
|
+
*/
|
|
30
|
+
export const readLive = (file: string): LiveFile | null => {
|
|
31
|
+
let json: unknown
|
|
32
|
+
try {
|
|
33
|
+
json = JSON.parse(readFileSync(file, 'utf8'))
|
|
34
|
+
} catch {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
return LiveFileSchema.parse(json)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The live file once `until` holds for it, or `null` after `ms`. */
|
|
41
|
+
export const waitForLive = async ({
|
|
42
|
+
file,
|
|
43
|
+
until,
|
|
44
|
+
ms = 3000,
|
|
45
|
+
}: {
|
|
46
|
+
file: string
|
|
47
|
+
until: (live: LiveFile) => boolean
|
|
48
|
+
ms?: number
|
|
49
|
+
}): Promise<LiveFile | null> => {
|
|
50
|
+
const deadline = Date.now() + ms
|
|
51
|
+
while (Date.now() < deadline) {
|
|
52
|
+
const live = readLive(file)
|
|
53
|
+
if (live !== null && until(live)) return live
|
|
54
|
+
await Bun.sleep(20)
|
|
55
|
+
}
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
@@ -189,14 +189,16 @@ export const HAPPY_TURNS: ScriptedTurn[] = [
|
|
|
189
189
|
]
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
* The turns, then
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
192
|
+
* The turns, then two clean final review rounds of spec #10 (round 1's, and
|
|
193
|
+
* the fresh pass's after a fix round, #548) and an empty learner to fall
|
|
194
|
+
* back on: the scripted launcher plays the first unused turn of a role, so
|
|
195
|
+
* lens and learner turns in `turns` go first, a lens the turns don't script
|
|
196
|
+
* approves, and a learner they don't script learns nothing.
|
|
196
197
|
*/
|
|
197
198
|
const withCleanLenses = (turns: ScriptedTurn[]): ScriptedTurn[] => [
|
|
198
199
|
...turns,
|
|
199
200
|
...CLEAN_LENS_TURNS(10),
|
|
201
|
+
...CLEAN_LENS_TURNS(10),
|
|
200
202
|
EMPTY_LEARNER_TURN(10),
|
|
201
203
|
]
|
|
202
204
|
|
|
@@ -347,6 +349,7 @@ export const createPracticeRepo = async ({
|
|
|
347
349
|
memory,
|
|
348
350
|
git: wrapGit,
|
|
349
351
|
journal: wrapJournal,
|
|
352
|
+
live_file,
|
|
350
353
|
}: {
|
|
351
354
|
/**
|
|
352
355
|
* Scripted agents' turns, then a clean final review for any lens they
|
|
@@ -385,6 +388,8 @@ export const createPracticeRepo = async ({
|
|
|
385
388
|
* Defaults to the journal as is.
|
|
386
389
|
*/
|
|
387
390
|
journal?: (real: Journal) => Journal
|
|
391
|
+
/** Where the engine keeps the run's live file (#628). Left out, it keeps none. */
|
|
392
|
+
live_file?: string
|
|
388
393
|
}) => {
|
|
389
394
|
const loaded = await loadEngineConfig({ repo_root: repo })
|
|
390
395
|
if (!loaded.ok) throw new Error(loaded.error)
|
|
@@ -419,6 +424,7 @@ export const createPracticeRepo = async ({
|
|
|
419
424
|
memory === undefined
|
|
420
425
|
? undefined
|
|
421
426
|
: { client: memory.client, timeout_ms: memory.timeout_ms },
|
|
427
|
+
live_file,
|
|
422
428
|
})
|
|
423
429
|
return {
|
|
424
430
|
action,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
* with a second ticket on top, so the board shows a blocked ticket waiting
|
|
21
21
|
* and then building. No GitHub, no models, no setup.
|
|
22
22
|
*
|
|
23
|
-
* Seam 2 (`core/run-one-ticket.test.ts`) runs on `practice-repo.ts` itself.
|
|
23
|
+
* Seam 2 (`core/run-one-ticket.e2e.test.ts`) runs on `practice-repo.ts` itself.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
export { makePracticeRepo, PRACTICE_SPEC_NUMBER }
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal entry builders for a stuck ticket and the owner's reply: the stuck
|
|
3
|
+
* report, a comment read, a reply word taken, and a ticket retried or skipped.
|
|
4
|
+
*/
|
|
5
|
+
import { SPEC_OWNER } from './intake-fixtures'
|
|
6
|
+
|
|
7
|
+
import type { JournalEntry } from '../journal/journal-record'
|
|
8
|
+
|
|
9
|
+
/** The engine told the spec issue that a ticket is stuck, in comment `comment_id`. */
|
|
10
|
+
export const stuckReported = ({
|
|
11
|
+
ticket,
|
|
12
|
+
comment_id,
|
|
13
|
+
}: {
|
|
14
|
+
ticket: number
|
|
15
|
+
/** Defaults to 100 + the ticket number. */
|
|
16
|
+
comment_id?: number
|
|
17
|
+
}): JournalEntry => ({
|
|
18
|
+
kind: 'stuck_reported',
|
|
19
|
+
ticket,
|
|
20
|
+
role: null,
|
|
21
|
+
content: { comment_id: comment_id ?? 100 + ticket, body: 'stuck' },
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
/** A comment the engine read on the spec issue. */
|
|
25
|
+
export const commentRead = ({
|
|
26
|
+
comment_id,
|
|
27
|
+
body,
|
|
28
|
+
author,
|
|
29
|
+
}: {
|
|
30
|
+
comment_id: number
|
|
31
|
+
body: string
|
|
32
|
+
/** Defaults to the spec's owner. */
|
|
33
|
+
author?: string
|
|
34
|
+
}): JournalEntry => ({
|
|
35
|
+
kind: 'comment_read',
|
|
36
|
+
ticket: null,
|
|
37
|
+
role: null,
|
|
38
|
+
content: { comment_id, author: author ?? SPEC_OWNER, body },
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
/** The engine took a reply word from the spec's owner. */
|
|
42
|
+
export const replyReceived = ({
|
|
43
|
+
word,
|
|
44
|
+
ticket,
|
|
45
|
+
comment_id,
|
|
46
|
+
}: {
|
|
47
|
+
word: 'retry' | 'skip' | 'stop' | 'ship'
|
|
48
|
+
ticket: number | null
|
|
49
|
+
comment_id: number
|
|
50
|
+
}): JournalEntry => ({
|
|
51
|
+
kind: 'reply_received',
|
|
52
|
+
ticket,
|
|
53
|
+
role: null,
|
|
54
|
+
content: { word, ticket, comment_id, author: SPEC_OWNER },
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
/** A retried ticket: resumed, started over, or refused. */
|
|
58
|
+
export const ticketRetried = ({
|
|
59
|
+
ticket,
|
|
60
|
+
mode,
|
|
61
|
+
}: {
|
|
62
|
+
ticket: number
|
|
63
|
+
mode: 'resume' | 'restart' | 'refused'
|
|
64
|
+
}): JournalEntry => ({
|
|
65
|
+
kind: 'ticket_retried',
|
|
66
|
+
ticket,
|
|
67
|
+
role: null,
|
|
68
|
+
content: {
|
|
69
|
+
mode,
|
|
70
|
+
base_sha: mode === 'restart' ? 'tip-sha' : null,
|
|
71
|
+
problems: mode === 'refused' ? ['no criteria'] : [],
|
|
72
|
+
answer_id: null,
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
/** A ticket left out of the run. */
|
|
77
|
+
export const ticketSkipped = ({
|
|
78
|
+
ticket,
|
|
79
|
+
because,
|
|
80
|
+
}: {
|
|
81
|
+
ticket: number
|
|
82
|
+
because?: number
|
|
83
|
+
}): JournalEntry => ({
|
|
84
|
+
kind: 'ticket_skipped',
|
|
85
|
+
ticket,
|
|
86
|
+
role: null,
|
|
87
|
+
content: { because: because ?? null },
|
|
88
|
+
})
|