@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
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/** A process the command started that still ran after a timeout's kill. */
|
|
2
|
+
export type Survivor = {
|
|
3
|
+
pid: number
|
|
4
|
+
/** Its command line, from `ps`. */
|
|
5
|
+
command: string
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
/** What a finished command left behind. */
|
|
2
9
|
export type CommandResult = {
|
|
3
10
|
/** `null` when the command was killed. */
|
|
@@ -5,6 +12,8 @@ export type CommandResult = {
|
|
|
5
12
|
stdout: string
|
|
6
13
|
stderr: string
|
|
7
14
|
timed_out: boolean
|
|
15
|
+
/** Set on a timeout only: what still ran after the kill (#571). */
|
|
16
|
+
survivors?: Survivor[]
|
|
8
17
|
}
|
|
9
18
|
|
|
10
19
|
/** Commands the engine runs give up after this long unless told otherwise. */
|
|
@@ -12,8 +21,9 @@ export const DEFAULT_COMMAND_TIMEOUT_MS = 300_000
|
|
|
12
21
|
|
|
13
22
|
/**
|
|
14
23
|
* After a timed-out command is killed, how long the engine still waits for
|
|
15
|
-
* its output
|
|
16
|
-
* output pipe open; past this, the
|
|
24
|
+
* its output when the caller doesn't say (`pipe_grace_ms`). A child that left
|
|
25
|
+
* the command's process group can hold the output pipe open; past this, the
|
|
26
|
+
* engine stops reading and moves on.
|
|
17
27
|
*/
|
|
18
28
|
const PIPE_GRACE_MS = 2_000
|
|
19
29
|
|
|
@@ -39,6 +49,110 @@ const signalGroup = ({
|
|
|
39
49
|
}
|
|
40
50
|
}
|
|
41
51
|
|
|
52
|
+
/** One line of `ps`: a process, its parent, and its process group. */
|
|
53
|
+
type ProcessEntry = Survivor & { ppid: number; pgid: number }
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Every process now running, from `ps`. Empty when `ps` fails: then the
|
|
57
|
+
* engine can only kill the command's own group.
|
|
58
|
+
*/
|
|
59
|
+
const listProcessTable = async (): Promise<ProcessEntry[]> => {
|
|
60
|
+
try {
|
|
61
|
+
const ps = Bun.spawn(
|
|
62
|
+
['ps', '-axww', '-o', 'pid=,ppid=,pgid=,command='],
|
|
63
|
+
{
|
|
64
|
+
stdin: 'ignore',
|
|
65
|
+
stdout: 'pipe',
|
|
66
|
+
stderr: 'ignore',
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
const [text, code] = await Promise.all([
|
|
70
|
+
new Response(ps.stdout).text(),
|
|
71
|
+
ps.exited,
|
|
72
|
+
])
|
|
73
|
+
if (code !== 0) return []
|
|
74
|
+
return text.split('\n').flatMap((line) => {
|
|
75
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s(.*)$/.exec(line)
|
|
76
|
+
if (match === null) return []
|
|
77
|
+
const [, pid, ppid, pgid, command] = match
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
pid: Number(pid),
|
|
81
|
+
ppid: Number(ppid),
|
|
82
|
+
pgid: Number(pgid),
|
|
83
|
+
command: (command ?? '').trim(),
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
})
|
|
87
|
+
} catch {
|
|
88
|
+
return []
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Every process below `pid`, found by parent process id. */
|
|
93
|
+
const descendantsOf = async (pid: number): Promise<ProcessEntry[]> => {
|
|
94
|
+
const table = await listProcessTable()
|
|
95
|
+
const found: ProcessEntry[] = []
|
|
96
|
+
const parents = new Set([pid])
|
|
97
|
+
for (let grew = true; grew; ) {
|
|
98
|
+
grew = false
|
|
99
|
+
for (const entry of table) {
|
|
100
|
+
if (!parents.has(entry.ppid) || parents.has(entry.pid)) continue
|
|
101
|
+
parents.add(entry.pid)
|
|
102
|
+
found.push(entry)
|
|
103
|
+
grew = true
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return found
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Whether a process with this id still runs. */
|
|
110
|
+
const isRunning = (pid: number): boolean => {
|
|
111
|
+
try {
|
|
112
|
+
process.kill(pid, 0)
|
|
113
|
+
return true
|
|
114
|
+
} catch {
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** How long the engine waits for killed processes to go before naming them. */
|
|
120
|
+
const SURVIVOR_WAIT_MS = 500
|
|
121
|
+
|
|
122
|
+
/** Of `processes`, those still running after up to `SURVIVOR_WAIT_MS`. */
|
|
123
|
+
const survivorsOf = async (processes: ProcessEntry[]): Promise<Survivor[]> => {
|
|
124
|
+
const end = Date.now() + SURVIVOR_WAIT_MS
|
|
125
|
+
let alive = processes.filter(({ pid }) => isRunning(pid))
|
|
126
|
+
while (alive.length > 0 && Date.now() < end) {
|
|
127
|
+
await Bun.sleep(25)
|
|
128
|
+
alive = alive.filter(({ pid }) => isRunning(pid))
|
|
129
|
+
}
|
|
130
|
+
return alive.map(({ pid, command }) => ({ pid, command }))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Kills a timed-out command and everything it started (#571). It first walks
|
|
135
|
+
* the process tree from the command, while the tree is still whole, then
|
|
136
|
+
* kills the command's group and the group of every process below it, so a
|
|
137
|
+
* child that started a group of its own goes too. Returns what it found.
|
|
138
|
+
*/
|
|
139
|
+
const killTree = async (pid: number): Promise<ProcessEntry[]> => {
|
|
140
|
+
const descendants = await descendantsOf(pid)
|
|
141
|
+
signalGroup({ pid, signal: 'SIGKILL' })
|
|
142
|
+
for (const entry of descendants) {
|
|
143
|
+
// Never the engine's own group.
|
|
144
|
+
if (entry.pgid > 1 && entry.pgid !== process.pid) {
|
|
145
|
+
signalGroup({ pid: entry.pgid, signal: 'SIGKILL' })
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
process.kill(entry.pid, 'SIGKILL')
|
|
149
|
+
} catch {
|
|
150
|
+
// Gone already.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return descendants
|
|
154
|
+
}
|
|
155
|
+
|
|
42
156
|
/** Sends `signal` to every command still running. */
|
|
43
157
|
const signalLiveGroups = (signal: NodeJS.Signals): void => {
|
|
44
158
|
for (const pid of liveGroups) signalGroup({ pid, signal })
|
|
@@ -97,10 +211,11 @@ const collectText = (
|
|
|
97
211
|
* Runs a command and collects its output. Never throws on a non-zero exit.
|
|
98
212
|
*
|
|
99
213
|
* The command runs in its own process group. One that runs past
|
|
100
|
-
* `timeout_ms` is killed with everything it started (
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
214
|
+
* `timeout_ms` is killed with everything it started (its group, and the
|
|
215
|
+
* group of every process below it) and marked `timed_out`, with
|
|
216
|
+
* `exit_code: null` and the `survivors` the kill missed. The engine then
|
|
217
|
+
* waits at most a moment more for its output (`pipe_grace_ms`, default
|
|
218
|
+
* `PIPE_GRACE_MS`), so a child that escaped the kill can't hold the engine.
|
|
104
219
|
*
|
|
105
220
|
* @example
|
|
106
221
|
* const result = await runCommand({ cmd: ['git', 'status'], cwd })
|
|
@@ -111,6 +226,7 @@ export const runCommand = async ({
|
|
|
111
226
|
cwd,
|
|
112
227
|
timeout_ms,
|
|
113
228
|
env,
|
|
229
|
+
pipe_grace_ms,
|
|
114
230
|
}: {
|
|
115
231
|
cmd: string[]
|
|
116
232
|
cwd: string
|
|
@@ -118,6 +234,11 @@ export const runCommand = async ({
|
|
|
118
234
|
timeout_ms?: number
|
|
119
235
|
/** Variables set on top of the engine's own environment. */
|
|
120
236
|
env?: Record<string, string>
|
|
237
|
+
/**
|
|
238
|
+
* How long to keep reading output after a timeout kill; defaults to
|
|
239
|
+
* `PIPE_GRACE_MS`. Tests of that wait pass a short one (#580).
|
|
240
|
+
*/
|
|
241
|
+
pipe_grace_ms?: number
|
|
121
242
|
}): Promise<CommandResult> => {
|
|
122
243
|
stopCommandsOnExit()
|
|
123
244
|
const proc = Bun.spawn({
|
|
@@ -133,16 +254,17 @@ export const runCommand = async ({
|
|
|
133
254
|
liveGroups.add(proc.pid)
|
|
134
255
|
const stdout = collectText(proc.stdout)
|
|
135
256
|
const stderr = collectText(proc.stderr)
|
|
136
|
-
let
|
|
257
|
+
let killed: Promise<ProcessEntry[]> | undefined
|
|
137
258
|
let grace: ReturnType<typeof setTimeout> | undefined
|
|
138
259
|
const timer = setTimeout(() => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
260
|
+
killed = killTree(proc.pid).then((descendants) => {
|
|
261
|
+
proc.kill('SIGKILL')
|
|
262
|
+
grace = setTimeout(() => {
|
|
263
|
+
stdout.stop()
|
|
264
|
+
stderr.stop()
|
|
265
|
+
}, pipe_grace_ms ?? PIPE_GRACE_MS)
|
|
266
|
+
return descendants
|
|
267
|
+
})
|
|
146
268
|
}, timeout_ms ?? DEFAULT_COMMAND_TIMEOUT_MS)
|
|
147
269
|
const [out, err, exitCode] = await Promise.all([
|
|
148
270
|
stdout.text,
|
|
@@ -150,14 +272,18 @@ export const runCommand = async ({
|
|
|
150
272
|
proc.exited,
|
|
151
273
|
])
|
|
152
274
|
clearTimeout(timer)
|
|
275
|
+
const descendants = await killed
|
|
153
276
|
clearTimeout(grace)
|
|
154
277
|
liveGroups.delete(proc.pid)
|
|
155
|
-
|
|
156
|
-
exit_code:
|
|
278
|
+
const result = {
|
|
279
|
+
exit_code: killed === undefined ? exitCode : null,
|
|
157
280
|
stdout: out,
|
|
158
281
|
stderr: err,
|
|
159
|
-
timed_out:
|
|
282
|
+
timed_out: killed !== undefined,
|
|
160
283
|
}
|
|
284
|
+
return descendants === undefined
|
|
285
|
+
? result
|
|
286
|
+
: { ...result, survivors: await survivorsOf(descendants) }
|
|
161
287
|
}
|
|
162
288
|
|
|
163
289
|
/** Runs a command line from the engine config through `sh -c`. */
|
|
@@ -165,12 +291,55 @@ export const runShell = ({
|
|
|
165
291
|
command,
|
|
166
292
|
cwd,
|
|
167
293
|
timeout_ms,
|
|
294
|
+
pipe_grace_ms,
|
|
168
295
|
}: {
|
|
169
296
|
command: string
|
|
170
297
|
cwd: string
|
|
171
298
|
timeout_ms?: number
|
|
299
|
+
/** See `runCommand`. */
|
|
300
|
+
pipe_grace_ms?: number
|
|
172
301
|
}): Promise<CommandResult> =>
|
|
173
|
-
runCommand({
|
|
302
|
+
runCommand({
|
|
303
|
+
cmd: ['sh', '-c', command],
|
|
304
|
+
cwd,
|
|
305
|
+
timeout_ms,
|
|
306
|
+
pipe_grace_ms,
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
/** A time limit in words: `30 minutes`, `1 minute`, `0.3 seconds`. */
|
|
310
|
+
export const durationText = (ms: number): string => {
|
|
311
|
+
const [amount, unit] =
|
|
312
|
+
ms >= 60_000 ? [ms / 60_000, 'minute'] : [ms / 1000, 'second']
|
|
313
|
+
const rounded = Math.round(amount * 10) / 10
|
|
314
|
+
return `${rounded} ${unit}${rounded === 1 ? '' : 's'}`
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* The line a timed-out command's output starts with: how long it had, that
|
|
319
|
+
* the engine stopped it, then `hint`, if any. With `survivors`, it names
|
|
320
|
+
* each one instead of claiming everything was stopped (#571).
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* timedOutLine({ timeout_ms: 300_000, hint: 'To give it longer, raise `test_timeout_ms`.' })
|
|
324
|
+
* // 'Timed out after 5 minutes, so the engine stopped it and everything it started. To give it longer, raise `test_timeout_ms`.\n'
|
|
325
|
+
*/
|
|
326
|
+
export const timedOutLine = ({
|
|
327
|
+
timeout_ms,
|
|
328
|
+
hint,
|
|
329
|
+
survivors,
|
|
330
|
+
}: {
|
|
331
|
+
timeout_ms: number
|
|
332
|
+
/** How to give the command longer. */
|
|
333
|
+
hint?: string
|
|
334
|
+
/** What still ran after the kill. */
|
|
335
|
+
survivors?: Survivor[]
|
|
336
|
+
}): string => {
|
|
337
|
+
const stopped =
|
|
338
|
+
survivors === undefined || survivors.length === 0
|
|
339
|
+
? 'so the engine stopped it and everything it started.'
|
|
340
|
+
: `so the engine stopped it. These were still running after the kill: ${survivors.map(({ pid, command }) => `pid ${pid} (${command})`).join(', ')}.`
|
|
341
|
+
return `Timed out after ${durationText(timeout_ms)}, ${stopped}${hint === undefined ? '' : ` ${hint}`}\n`
|
|
342
|
+
}
|
|
174
343
|
|
|
175
344
|
/** Keeps the start and end of long output, for the journal and prompts. */
|
|
176
345
|
export const clipOutput = ({
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal entry builders for agent turns: a turn started, its result for
|
|
3
|
+
* each role (tests written, implemented, reviewed), a failed turn, and the
|
|
4
|
+
* launcher's summary of a turn.
|
|
5
|
+
*/
|
|
6
|
+
import type {
|
|
7
|
+
AgentRole,
|
|
8
|
+
CriterionTests,
|
|
9
|
+
Finding,
|
|
10
|
+
FindingResponse,
|
|
11
|
+
FindingRuling,
|
|
12
|
+
} from '../agents/role-results'
|
|
13
|
+
import type { JournalEntry } from '../journal/journal-record'
|
|
14
|
+
|
|
15
|
+
/** The session id fixtures give each role's turns unless told otherwise. */
|
|
16
|
+
export const SESSIONS = {
|
|
17
|
+
'test-writer': 'tw-1',
|
|
18
|
+
implementer: 'impl-1',
|
|
19
|
+
'ticket-reviewer': 'rev-1',
|
|
20
|
+
'architecture-lens': 'architecture-1',
|
|
21
|
+
'simplification-lens': 'simplification-1',
|
|
22
|
+
'security-lens': 'security-1',
|
|
23
|
+
'integration-lens': 'integration-1',
|
|
24
|
+
'rules-lens': 'rules-1',
|
|
25
|
+
learner: 'learner-1',
|
|
26
|
+
} as const satisfies Record<AgentRole, string>
|
|
27
|
+
|
|
28
|
+
/** An agent turn started: a fresh launch, or a follow-up to `follow_up_of`. */
|
|
29
|
+
export const agentStarted = ({
|
|
30
|
+
ticket,
|
|
31
|
+
role,
|
|
32
|
+
prompt,
|
|
33
|
+
follow_up_of,
|
|
34
|
+
}: {
|
|
35
|
+
/** `null` for the final review. */
|
|
36
|
+
ticket: number | null
|
|
37
|
+
role: AgentRole
|
|
38
|
+
prompt?: string
|
|
39
|
+
follow_up_of?: string
|
|
40
|
+
}): JournalEntry => ({
|
|
41
|
+
kind: 'agent_started',
|
|
42
|
+
ticket,
|
|
43
|
+
role,
|
|
44
|
+
content: {
|
|
45
|
+
role,
|
|
46
|
+
prompt: prompt ?? 'p',
|
|
47
|
+
follow_up_of: follow_up_of ?? null,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const testsWritten = ({
|
|
52
|
+
ticket,
|
|
53
|
+
session_id,
|
|
54
|
+
assumptions,
|
|
55
|
+
finding_responses,
|
|
56
|
+
run_notes,
|
|
57
|
+
criteria,
|
|
58
|
+
}: {
|
|
59
|
+
/** `null` for the final review. */
|
|
60
|
+
ticket: number | null
|
|
61
|
+
/** Defaults to `SESSIONS['test-writer']`. */
|
|
62
|
+
session_id?: string
|
|
63
|
+
/** Defaults to one test, `sum adds two numbers`, for AC1. */
|
|
64
|
+
criteria?: CriterionTests[]
|
|
65
|
+
/** Defaults to one assumption. */
|
|
66
|
+
assumptions?: string[]
|
|
67
|
+
/** A review fixer's answer to each finding it got. */
|
|
68
|
+
finding_responses?: FindingResponse[]
|
|
69
|
+
/** Defaults to none. */
|
|
70
|
+
run_notes?: string[]
|
|
71
|
+
}): JournalEntry => ({
|
|
72
|
+
kind: 'agent_finished',
|
|
73
|
+
ticket,
|
|
74
|
+
role: 'test-writer',
|
|
75
|
+
content: {
|
|
76
|
+
role: 'test-writer',
|
|
77
|
+
session_id: session_id ?? SESSIONS['test-writer'],
|
|
78
|
+
result: {
|
|
79
|
+
outcome: 'tests_written',
|
|
80
|
+
criteria: criteria ?? [
|
|
81
|
+
{
|
|
82
|
+
criterion_id: 'AC1',
|
|
83
|
+
tests: [
|
|
84
|
+
{
|
|
85
|
+
file: 'src/sum.test.ts',
|
|
86
|
+
name: 'sum adds two numbers',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
summary: 'One test for AC1.',
|
|
92
|
+
assumptions: assumptions ?? ['Numbers are integers.'],
|
|
93
|
+
run_notes: run_notes ?? [],
|
|
94
|
+
finding_responses: finding_responses ?? [],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
/** A test-writer that found nothing new to test. */
|
|
100
|
+
export const nothingNewToTest = ({
|
|
101
|
+
ticket,
|
|
102
|
+
}: {
|
|
103
|
+
ticket: number
|
|
104
|
+
}): JournalEntry => ({
|
|
105
|
+
kind: 'agent_finished',
|
|
106
|
+
ticket,
|
|
107
|
+
role: 'test-writer',
|
|
108
|
+
content: {
|
|
109
|
+
role: 'test-writer',
|
|
110
|
+
session_id: SESSIONS['test-writer'],
|
|
111
|
+
result: { outcome: 'nothing_new_to_test' },
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
export const implemented = ({
|
|
116
|
+
ticket,
|
|
117
|
+
outcome,
|
|
118
|
+
session_id,
|
|
119
|
+
reason,
|
|
120
|
+
assumptions,
|
|
121
|
+
finding_responses,
|
|
122
|
+
run_notes,
|
|
123
|
+
}: {
|
|
124
|
+
/** `null` for the final review. */
|
|
125
|
+
ticket: number | null
|
|
126
|
+
outcome?: 'done' | 'bad_test'
|
|
127
|
+
/** Defaults to `SESSIONS.implementer`. */
|
|
128
|
+
session_id?: string
|
|
129
|
+
/** The bad test's reason. Defaults to "Wrong sum." */
|
|
130
|
+
reason?: string
|
|
131
|
+
assumptions?: string[]
|
|
132
|
+
finding_responses?: FindingResponse[]
|
|
133
|
+
/** Defaults to none. */
|
|
134
|
+
run_notes?: string[]
|
|
135
|
+
}): JournalEntry => ({
|
|
136
|
+
kind: 'agent_finished',
|
|
137
|
+
ticket,
|
|
138
|
+
role: 'implementer',
|
|
139
|
+
content: {
|
|
140
|
+
role: 'implementer',
|
|
141
|
+
session_id: session_id ?? SESSIONS.implementer,
|
|
142
|
+
result: {
|
|
143
|
+
outcome: outcome ?? 'done',
|
|
144
|
+
bad_test:
|
|
145
|
+
outcome === 'bad_test'
|
|
146
|
+
? {
|
|
147
|
+
file: 'src/sum.test.ts',
|
|
148
|
+
name: 'sum adds two numbers',
|
|
149
|
+
reason: reason ?? 'Wrong sum.',
|
|
150
|
+
}
|
|
151
|
+
: null,
|
|
152
|
+
summary: 'Added sum.',
|
|
153
|
+
assumptions: assumptions ?? [],
|
|
154
|
+
run_notes: run_notes ?? [],
|
|
155
|
+
finding_responses: finding_responses ?? [],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
/** One reviewer finding; a code should-fix in `src/sum.ts` unless told otherwise. */
|
|
161
|
+
export const finding = ({
|
|
162
|
+
id,
|
|
163
|
+
severity,
|
|
164
|
+
kind,
|
|
165
|
+
title,
|
|
166
|
+
file,
|
|
167
|
+
}: {
|
|
168
|
+
id: string
|
|
169
|
+
severity?: 'blocker' | 'should_fix' | 'nit'
|
|
170
|
+
kind?: 'code' | 'test'
|
|
171
|
+
title?: string
|
|
172
|
+
file?: string | null
|
|
173
|
+
}): Finding => ({
|
|
174
|
+
id,
|
|
175
|
+
severity: severity ?? 'should_fix',
|
|
176
|
+
kind: kind ?? 'code',
|
|
177
|
+
file: file === undefined ? 'src/sum.ts' : file,
|
|
178
|
+
title: title ?? `Finding ${id}`,
|
|
179
|
+
detail: `Detail of ${id}.`,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A ticket reviewer's result. The verdict follows the findings: changes are
|
|
184
|
+
* requested when any is a blocker or should-fix.
|
|
185
|
+
*/
|
|
186
|
+
export const reviewed = ({
|
|
187
|
+
ticket,
|
|
188
|
+
findings,
|
|
189
|
+
rulings,
|
|
190
|
+
session_id,
|
|
191
|
+
}: {
|
|
192
|
+
ticket: number
|
|
193
|
+
findings?: Finding[]
|
|
194
|
+
rulings?: FindingRuling[]
|
|
195
|
+
/** Defaults to `SESSIONS['ticket-reviewer']`. */
|
|
196
|
+
session_id?: string
|
|
197
|
+
}): JournalEntry => {
|
|
198
|
+
const list = findings ?? []
|
|
199
|
+
const blocking = list.some(({ severity }) => severity !== 'nit')
|
|
200
|
+
return {
|
|
201
|
+
kind: 'agent_finished',
|
|
202
|
+
ticket,
|
|
203
|
+
role: 'ticket-reviewer',
|
|
204
|
+
content: {
|
|
205
|
+
role: 'ticket-reviewer',
|
|
206
|
+
session_id: session_id ?? SESSIONS['ticket-reviewer'],
|
|
207
|
+
result: {
|
|
208
|
+
verdict: blocking ? 'changes_requested' : 'approve',
|
|
209
|
+
findings: list,
|
|
210
|
+
rulings: rulings ?? [],
|
|
211
|
+
summary: 'Looks right.',
|
|
212
|
+
assumptions: [],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** An agent turn failed, in the session its role's fixtures use unless told otherwise. */
|
|
219
|
+
export const agentFailed = ({
|
|
220
|
+
ticket,
|
|
221
|
+
role,
|
|
222
|
+
failure,
|
|
223
|
+
error,
|
|
224
|
+
session_id,
|
|
225
|
+
session_gone,
|
|
226
|
+
out_of_turns,
|
|
227
|
+
}: {
|
|
228
|
+
/** `null` for the final review. */
|
|
229
|
+
ticket: number | null
|
|
230
|
+
role: AgentRole
|
|
231
|
+
failure: 'agent' | 'result' | 'guard' | 'engine'
|
|
232
|
+
/** Defaults to "It broke." */
|
|
233
|
+
error?: string
|
|
234
|
+
/** Defaults to `SESSIONS[role]`; `null` for a turn with no session. */
|
|
235
|
+
session_id?: string | null
|
|
236
|
+
/** A follow-up found its session closed or gone (#521). Left out by default. */
|
|
237
|
+
session_gone?: boolean
|
|
238
|
+
/** The turn reached its role's turn limit (#543). Left out by default. */
|
|
239
|
+
out_of_turns?: boolean
|
|
240
|
+
}): JournalEntry => ({
|
|
241
|
+
kind: 'agent_failed',
|
|
242
|
+
ticket,
|
|
243
|
+
role,
|
|
244
|
+
content: {
|
|
245
|
+
role,
|
|
246
|
+
failure,
|
|
247
|
+
error: error ?? 'It broke.',
|
|
248
|
+
session_id: session_id === undefined ? SESSIONS[role] : session_id,
|
|
249
|
+
...(session_gone === undefined ? {} : { session_gone }),
|
|
250
|
+
...(out_of_turns === undefined ? {} : { out_of_turns }),
|
|
251
|
+
},
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
/** The launcher's summary of one agent turn, with these readings and tokens. */
|
|
255
|
+
export const agentSession = ({
|
|
256
|
+
ticket,
|
|
257
|
+
role,
|
|
258
|
+
rate_limit_events,
|
|
259
|
+
billing_error,
|
|
260
|
+
output_tokens,
|
|
261
|
+
}: {
|
|
262
|
+
/** `null` for the final review. */
|
|
263
|
+
ticket: number | null
|
|
264
|
+
role: AgentRole
|
|
265
|
+
rate_limit_events?: Record<string, unknown>[]
|
|
266
|
+
billing_error?: boolean
|
|
267
|
+
/** Defaults to 100; input tokens are always 10. */
|
|
268
|
+
output_tokens?: number
|
|
269
|
+
}): JournalEntry => ({
|
|
270
|
+
kind: 'agent_session',
|
|
271
|
+
ticket,
|
|
272
|
+
role,
|
|
273
|
+
content: {
|
|
274
|
+
role,
|
|
275
|
+
session: {
|
|
276
|
+
session_id: SESSIONS[role],
|
|
277
|
+
usage: {
|
|
278
|
+
input_tokens: 10,
|
|
279
|
+
output_tokens: output_tokens ?? 100,
|
|
280
|
+
cache_read_input_tokens: 0,
|
|
281
|
+
cache_creation_input_tokens: 0,
|
|
282
|
+
},
|
|
283
|
+
rate_limit_events: rate_limit_events ?? [],
|
|
284
|
+
billing_error: billing_error ?? false,
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal entry builders for a ticket whose work is already on the base
|
|
3
|
+
* branch: the test-writer's evidence, the engine's check of it, and the
|
|
4
|
+
* ticket noted and closed.
|
|
5
|
+
*/
|
|
6
|
+
import { SESSIONS } from './agent-turn-fixtures'
|
|
7
|
+
|
|
8
|
+
import type { JournalEntry } from '../journal/journal-record'
|
|
9
|
+
|
|
10
|
+
/** The commit fixtures say did an already-done ticket's work. */
|
|
11
|
+
export const DONE_SHA = '3559c25f5a1b2c3d4e5f60718293a4b5c6d7e8f9'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A test-writer that found the ticket's work already on the base branch:
|
|
15
|
+
* done by `DONE_SHA` unless told otherwise, with one existing test for AC1.
|
|
16
|
+
*/
|
|
17
|
+
export const alreadyDone = ({
|
|
18
|
+
ticket,
|
|
19
|
+
shas,
|
|
20
|
+
}: {
|
|
21
|
+
ticket: number
|
|
22
|
+
/** Defaults to `[DONE_SHA]`. */
|
|
23
|
+
shas?: string[]
|
|
24
|
+
}): JournalEntry => ({
|
|
25
|
+
kind: 'agent_finished',
|
|
26
|
+
ticket,
|
|
27
|
+
role: 'test-writer',
|
|
28
|
+
content: {
|
|
29
|
+
role: 'test-writer',
|
|
30
|
+
session_id: SESSIONS['test-writer'],
|
|
31
|
+
result: {
|
|
32
|
+
outcome: 'already_done',
|
|
33
|
+
done_by: (shas ?? [DONE_SHA]).map((sha) => ({
|
|
34
|
+
sha,
|
|
35
|
+
title: 'feat: add sum',
|
|
36
|
+
})),
|
|
37
|
+
criteria: [
|
|
38
|
+
{
|
|
39
|
+
criterion_id: 'AC1',
|
|
40
|
+
tests: [
|
|
41
|
+
{
|
|
42
|
+
file: 'src/sum.test.ts',
|
|
43
|
+
name: 'sum adds two numbers',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
summary: 'The sum is already on main, with its test.',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The engine checked a test-writer's "already done" evidence (#495): it
|
|
55
|
+
* passed, or, with `problems`, it did not.
|
|
56
|
+
*/
|
|
57
|
+
export const alreadyDoneChecked = ({
|
|
58
|
+
ticket,
|
|
59
|
+
problems,
|
|
60
|
+
}: {
|
|
61
|
+
ticket: number
|
|
62
|
+
/** What did not check out. Left out, none: the check passed. */
|
|
63
|
+
problems?: string[]
|
|
64
|
+
}): JournalEntry => ({
|
|
65
|
+
kind: 'already_done_checked',
|
|
66
|
+
ticket,
|
|
67
|
+
role: null,
|
|
68
|
+
content: {
|
|
69
|
+
ok: (problems ?? []).length === 0,
|
|
70
|
+
problems: problems ?? [],
|
|
71
|
+
base_sha: 'b0',
|
|
72
|
+
tests: null,
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
/** The engine noted that a ticket's work is already done, on the spec issue. */
|
|
77
|
+
export const ticketAlreadyDone = ({
|
|
78
|
+
ticket,
|
|
79
|
+
shas,
|
|
80
|
+
}: {
|
|
81
|
+
ticket: number
|
|
82
|
+
/** Defaults to `[DONE_SHA]`. */
|
|
83
|
+
shas?: string[]
|
|
84
|
+
}): JournalEntry => ({
|
|
85
|
+
kind: 'ticket_already_done',
|
|
86
|
+
ticket,
|
|
87
|
+
role: null,
|
|
88
|
+
content: { shas: shas ?? [DONE_SHA], comment_id: 300 + ticket },
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
/** The engine closed an already-done ticket, with a comment on it. */
|
|
92
|
+
export const ticketClosed = ({ ticket }: { ticket: number }): JournalEntry => ({
|
|
93
|
+
kind: 'ticket_closed',
|
|
94
|
+
ticket,
|
|
95
|
+
role: null,
|
|
96
|
+
content: { comment_id: 400 + ticket },
|
|
97
|
+
})
|