@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,322 +1,30 @@
|
|
|
1
|
-
import compact from 'lodash/compact'
|
|
2
|
-
import max from 'lodash/max'
|
|
3
|
-
|
|
4
|
-
import type { AgentRole } from '../agents/role-results'
|
|
5
|
-
import type { ReplyProblem, StuckReason } from '../journal/journal-record'
|
|
6
|
-
import type {
|
|
7
|
-
FinalReviewState,
|
|
8
|
-
ReplayedStuck,
|
|
9
|
-
ReplayedWorktree,
|
|
10
|
-
TicketProgress,
|
|
11
|
-
} from '../journal/replay'
|
|
12
|
-
import { REFACTOR_LABEL } from '../tracker/tracker'
|
|
13
|
-
|
|
14
|
-
/** Longest error text a comment or prompt quotes. */
|
|
15
|
-
const DETAIL_MAX = 3000
|
|
16
|
-
|
|
17
|
-
/** Why a ticket is stuck, in one line. */
|
|
18
|
-
const REASON_LINES: Record<StuckReason, string> = {
|
|
19
|
-
agent_failed: 'An agent failed on every try it had.',
|
|
20
|
-
red_check_failed:
|
|
21
|
-
"The red check still fails: the new tests don't fail the way they should.",
|
|
22
|
-
nothing_new_to_test:
|
|
23
|
-
'The test-writer says the ticket changes no behavior, so there is nothing new to test: it looks like a refactor.',
|
|
24
|
-
leftovers_found:
|
|
25
|
-
'The leftover scan found files that must not be committed.',
|
|
26
|
-
gates_failed: 'The checks still fail.',
|
|
27
|
-
bad_test:
|
|
28
|
-
'The implementer sent a test back as bad, and it cannot go back again.',
|
|
29
|
-
changes_requested: 'The ticket review still asks for changes.',
|
|
30
|
-
join_failed: 'The ticket still clashes with the run branch.',
|
|
31
|
-
join_gates_failed:
|
|
32
|
-
'The checks still fail once the ticket joins the run branch.',
|
|
33
|
-
install_failed: 'Installing the dependencies failed.',
|
|
34
|
-
prepare_failed:
|
|
35
|
-
"The repo's prepare command failed before any agent worked.",
|
|
36
|
-
setup_change_needed:
|
|
37
|
-
'An agent needs a test setup file changed, and only you may change one.',
|
|
38
|
-
crashed: 'The engine crashed in the same step, again and again.',
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** What the user could do, per reason; `n` is the ticket. */
|
|
42
|
-
const suggestion = ({
|
|
43
|
-
reason,
|
|
44
|
-
n,
|
|
45
|
-
}: {
|
|
46
|
-
reason: StuckReason
|
|
47
|
-
n: number
|
|
48
|
-
}): string => {
|
|
49
|
-
const retry = `\`retry #${n}\``
|
|
50
|
-
switch (reason) {
|
|
51
|
-
case 'agent_failed':
|
|
52
|
-
return `Read the last error, fix what trips the agent (in the ticket or its worktree), then reply ${retry}.`
|
|
53
|
-
case 'red_check_failed':
|
|
54
|
-
return `Make the ticket's criteria clearer, or fix the tests in the worktree yourself, then reply ${retry}.`
|
|
55
|
-
case 'nothing_new_to_test':
|
|
56
|
-
return `A ticket that changes no behavior is a refactor ticket: add the \`${REFACTOR_LABEL}\` label, then reply ${retry}. It is built again as a refactor, with no new tests.`
|
|
57
|
-
case 'leftovers_found':
|
|
58
|
-
return `Delete those files in the worktree (or move them out of it), then reply ${retry}.`
|
|
59
|
-
case 'gates_failed':
|
|
60
|
-
return `Fix the failing check in the worktree, or make the ticket clearer, then reply ${retry}.`
|
|
61
|
-
case 'bad_test':
|
|
62
|
-
return `Fix or remove that test in the worktree, or make the ticket clearer, then reply ${retry}.`
|
|
63
|
-
case 'changes_requested':
|
|
64
|
-
return `Fix the open findings in the worktree, then reply ${retry}; or reply \`skip #${n}\` if they can wait.`
|
|
65
|
-
case 'join_failed':
|
|
66
|
-
return `Reply ${retry} to put it on the run branch's tip again (uncommitted edits in its worktree are replaced), or \`skip #${n}\`.`
|
|
67
|
-
case 'join_gates_failed':
|
|
68
|
-
return `Its join was undone, so the run branch is safe. Reply ${retry} to join again and fix it on top of the run branch, or \`skip #${n}\`.`
|
|
69
|
-
case 'install_failed':
|
|
70
|
-
return `Fix the manifest or lockfile on the base branch, then reply ${retry}.`
|
|
71
|
-
case 'prepare_failed':
|
|
72
|
-
return `Fix what the prepare command needs on this computer (a missing tool, say), or the command in \`.luca/config.json\`, then reply ${retry}. If it ran out of time, raise \`prepare_timeout_ms\` there (30 minutes by default).`
|
|
73
|
-
case 'setup_change_needed':
|
|
74
|
-
return `Make that change yourself in the worktree, then reply ${retry}.`
|
|
75
|
-
case 'crashed':
|
|
76
|
-
return `Read the engine's log for why it crashed, fix what trips it, then reply ${retry} to take the step again with a fresh agent.`
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** A code fence longer than any run of backticks in `text`. */
|
|
81
|
-
const fenced = (text: string): string => {
|
|
82
|
-
const longest = max((text.match(/`+/g) ?? []).map(({ length }) => length))
|
|
83
|
-
const fence = '`'.repeat(Math.max(3, (longest ?? 0) + 1))
|
|
84
|
-
const clipped =
|
|
85
|
-
text.length > DETAIL_MAX
|
|
86
|
-
? `${text.slice(0, DETAIL_MAX)}\n... (clipped)`
|
|
87
|
-
: text
|
|
88
|
-
return `${fence}\n${clipped}\n${fence}`
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const plural = (count: number, word: string): string =>
|
|
92
|
-
`${count} ${word}${count === 1 ? '' : 's'}`
|
|
93
|
-
|
|
94
|
-
/** What the engine already tried on a ticket, from its counts. */
|
|
95
|
-
export const triedText = ({
|
|
96
|
-
progress,
|
|
97
|
-
}: {
|
|
98
|
-
progress: TicketProgress
|
|
99
|
-
}): string => {
|
|
100
|
-
const tries = Object.entries(progress.failed_tries).flatMap(
|
|
101
|
-
([role, count]) =>
|
|
102
|
-
count === undefined || count === 0
|
|
103
|
-
? []
|
|
104
|
-
: [`${plural(count, 'failed try')} by the ${role}`]
|
|
105
|
-
)
|
|
106
|
-
const reviewFixRounds =
|
|
107
|
-
progress.review_fix === null
|
|
108
|
-
? 0
|
|
109
|
-
: Math.max(0, progress.review_fix.round - 1)
|
|
110
|
-
const parts = compact([
|
|
111
|
-
progress.red_fix_rounds > 0 &&
|
|
112
|
-
`${plural(progress.red_fix_rounds, 'fix round')} on the red check`,
|
|
113
|
-
progress.gate_fix_rounds > 0 &&
|
|
114
|
-
`${plural(progress.gate_fix_rounds, 'fix round')} on the checks`,
|
|
115
|
-
progress.bad_test_bounces > 0 &&
|
|
116
|
-
`${plural(progress.bad_test_bounces, 'bad test')} sent back`,
|
|
117
|
-
reviewFixRounds > 0 && plural(reviewFixRounds, 'review fix round'),
|
|
118
|
-
progress.rejoins > 0 &&
|
|
119
|
-
`${plural(progress.rejoins, 'rebase')} onto the run branch`,
|
|
120
|
-
progress.engine_failures > 0 &&
|
|
121
|
-
`${plural(progress.engine_failures, 'engine failure')} in a row`,
|
|
122
|
-
...tries,
|
|
123
|
-
])
|
|
124
|
-
return parts.length === 0
|
|
125
|
-
? 'Nothing more: another try would fail the same way.'
|
|
126
|
-
: `${parts.join(', ')}.`
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** Why a ticket is stuck, in one line. */
|
|
130
|
-
export const reasonLine = ({ reason }: { reason: StuckReason }): string =>
|
|
131
|
-
REASON_LINES[reason]
|
|
132
|
-
|
|
133
1
|
/**
|
|
134
|
-
* The
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* @example
|
|
138
|
-
* stuckComment({ ticket: 11, title: 'Add sum', progress, stuck })
|
|
139
|
-
* // '**Ticket #11 is stuck: Add sum**\n\nWhy: The checks still fail. ...'
|
|
2
|
+
* The texts for stuck work and the replies to it: a skipped ticket's comment
|
|
3
|
+
* and answers to replies that can't be used. The stuck comments live in the
|
|
4
|
+
* modules it re-exports.
|
|
140
5
|
*/
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
]
|
|
164
|
-
.filter(Boolean)
|
|
165
|
-
.join('\n'),
|
|
166
|
-
`Last error:\n\n${fenced(stuck.detail)}`,
|
|
167
|
-
[
|
|
168
|
-
'Reply with one word on this issue:',
|
|
169
|
-
`- \`retry #${ticket}\`: pick up where it stopped with a fresh agent, or start it over if you changed the ticket's text or labels.`,
|
|
170
|
-
`- \`skip #${ticket}\`: leave it out, with the tickets that wait on it, and ship the rest. They stay open.`,
|
|
171
|
-
'- `stop`: end the run without a PR. The branch is kept.',
|
|
172
|
-
'',
|
|
173
|
-
'Only the spec owner counts. With one ticket stuck, the bare word works too. Other tickets keep building meanwhile.',
|
|
174
|
-
].join('\n'),
|
|
175
|
-
]).join('\n\n')
|
|
176
|
-
|
|
177
|
-
/** Why the final review is stuck, in one line, where it differs from a ticket. */
|
|
178
|
-
const FINAL_REASON_LINES: Partial<Record<StuckReason, string>> = {
|
|
179
|
-
changes_requested: 'The final review still asks for changes.',
|
|
180
|
-
gates_failed: "The checks still fail after the final review's fixes.",
|
|
181
|
-
bad_test:
|
|
182
|
-
"While fixing the final review's findings, the implementer sent a test back as bad.",
|
|
183
|
-
agent_failed: 'A final review agent failed on every try it had.',
|
|
184
|
-
crashed:
|
|
185
|
-
'The engine crashed in the same final review step, again and again.',
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** What the owner could do about the stuck final review, per reason. */
|
|
189
|
-
const finalSuggestion = ({ reason }: { reason: StuckReason }): string => {
|
|
190
|
-
switch (reason) {
|
|
191
|
-
case 'changes_requested':
|
|
192
|
-
return "Fix the open findings in the run branch's worktree, then reply `retry`; or reply `ship` if they can wait for review in the PR."
|
|
193
|
-
case 'leftovers_found':
|
|
194
|
-
return "Delete those files in the run branch's worktree, then reply `retry`."
|
|
195
|
-
default:
|
|
196
|
-
return "Read the last error, fix what trips it in the run branch's worktree, then reply `retry`; or reply `ship` to open the PR anyway."
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* The comment on the spec issue when the final review is stuck: why (one
|
|
202
|
-
* line), what was tried, the last error, a suggestion, and the replies:
|
|
203
|
-
* `retry`, `stop`, and `ship` (never `skip`).
|
|
204
|
-
*
|
|
205
|
-
* @example
|
|
206
|
-
* finalStuckComment({ review, run_branch })
|
|
207
|
-
* // '**The final review is stuck**\n\nWhy: The final review still asks for changes. ...'
|
|
208
|
-
*/
|
|
209
|
-
export const finalStuckComment = ({
|
|
210
|
-
review,
|
|
211
|
-
run_branch,
|
|
212
|
-
}: {
|
|
213
|
-
review: FinalReviewState
|
|
214
|
-
run_branch: ReplayedWorktree
|
|
215
|
-
}): string => {
|
|
216
|
-
const stuck = review.stuck ?? { reason: 'agent_failed', detail: '' }
|
|
217
|
-
const tries = Object.entries(review.failed_tries).flatMap(
|
|
218
|
-
([role, count]) =>
|
|
219
|
-
count === undefined || count === 0
|
|
220
|
-
? []
|
|
221
|
-
: [`${plural(count, 'failed try')} by the ${role}`]
|
|
222
|
-
)
|
|
223
|
-
const tried = compact([
|
|
224
|
-
review.round > 1 && plural(review.round - 1, 'fix round'),
|
|
225
|
-
review.gate_fix_rounds > 0 &&
|
|
226
|
-
`${plural(review.gate_fix_rounds, 'fix round')} on the checks`,
|
|
227
|
-
...tries,
|
|
228
|
-
])
|
|
229
|
-
return [
|
|
230
|
-
'**The final review is stuck**',
|
|
231
|
-
[
|
|
232
|
-
`Why: ${FINAL_REASON_LINES[stuck.reason] ?? reasonLine({ reason: stuck.reason })}`,
|
|
233
|
-
`Tried: ${tried.length === 0 ? 'Nothing more: another try would fail the same way.' : `${tried.join(', ')}.`}`,
|
|
234
|
-
`Suggestion: ${finalSuggestion({ reason: stuck.reason })}`,
|
|
235
|
-
`Run branch worktree: \`${run_branch.path}\` (a \`retry\` keeps your edits there)`,
|
|
236
|
-
].join('\n'),
|
|
237
|
-
`Last error:\n\n${fenced(stuck.detail)}`,
|
|
238
|
-
[
|
|
239
|
-
'Reply with one word on this issue:',
|
|
240
|
-
'- `retry`: fix the open findings again with fresh agents and fresh counts, keeping your edits.',
|
|
241
|
-
'- `ship`: open the PR anyway, with the open findings listed at the top.',
|
|
242
|
-
'- `stop`: end the run without a PR. The branch is kept.',
|
|
243
|
-
'',
|
|
244
|
-
'Only the spec owner counts.',
|
|
245
|
-
].join('\n'),
|
|
246
|
-
].join('\n\n')
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** A token count with thousands separators, such as `50,000`. */
|
|
250
|
-
const tokensText = (tokens: number): string =>
|
|
251
|
-
String(tokens).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Why the run is stuck on its budget, in one line.
|
|
255
|
-
*
|
|
256
|
-
* @example
|
|
257
|
-
* runBudgetDetail({ tokens: 50_100, budget: 50_000 })
|
|
258
|
-
* // 'The run used 50,100 tokens of its run budget of 50,000.'
|
|
259
|
-
*/
|
|
260
|
-
export const runBudgetDetail = ({
|
|
261
|
-
tokens,
|
|
262
|
-
budget,
|
|
263
|
-
}: {
|
|
264
|
-
tokens: number
|
|
265
|
-
budget: number
|
|
266
|
-
}): string =>
|
|
267
|
-
`The run used ${tokensText(tokens)} tokens of its run budget of ${tokensText(budget)}.`
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* The comment on the spec issue when the whole run is stuck on its run
|
|
271
|
-
* budget: why, how big one budget is, and the replies `retry` and `stop`
|
|
272
|
-
* (never `skip` or `ship`).
|
|
273
|
-
*
|
|
274
|
-
* @example
|
|
275
|
-
* runStuckComment({ detail, budget: 9_000_000 })
|
|
276
|
-
* // '**The run is stuck: it used up its run budget**\n\nWhy: ...'
|
|
277
|
-
*/
|
|
278
|
-
export const runStuckComment = ({
|
|
279
|
-
detail,
|
|
280
|
-
budget,
|
|
281
|
-
}: {
|
|
282
|
-
detail: string
|
|
283
|
-
/** One full run budget. */
|
|
284
|
-
budget: number
|
|
285
|
-
}): string =>
|
|
286
|
-
[
|
|
287
|
-
'**The run is stuck: it used up its run budget**',
|
|
288
|
-
[
|
|
289
|
-
`Why: ${detail}`,
|
|
290
|
-
`One run budget is ${tokensText(budget)} tokens (input, output, and cache writes; cache reads don't count). A repo changes it with \`run_budget_tokens\` in \`.luca/config.json\`.`,
|
|
291
|
-
'Nothing new starts until you reply. Other stuck work waits for its reply until the run carries on.',
|
|
292
|
-
].join('\n'),
|
|
293
|
-
[
|
|
294
|
-
'Reply with one word on this issue:',
|
|
295
|
-
`- \`retry\`: add one more full run budget (${tokensText(budget)} tokens) and carry on where it stopped.`,
|
|
296
|
-
'- `stop`: end the run without a PR. The branch is kept.',
|
|
297
|
-
'',
|
|
298
|
-
'Only the spec owner counts.',
|
|
299
|
-
].join('\n'),
|
|
300
|
-
].join('\n\n')
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* The detail of a ticket stuck on a test setup file change: the file, why,
|
|
304
|
-
* and what to do.
|
|
305
|
-
*/
|
|
306
|
-
export const setupChangeDetail = ({
|
|
307
|
-
role,
|
|
308
|
-
file,
|
|
309
|
-
reason,
|
|
310
|
-
setup_files,
|
|
311
|
-
}: {
|
|
312
|
-
role: AgentRole
|
|
313
|
-
file: string
|
|
314
|
-
reason: string
|
|
315
|
-
/** The config's test setup files. */
|
|
316
|
-
setup_files: string[]
|
|
317
|
-
}): string =>
|
|
318
|
-
`The ${role} needs the test setup file \`${file || 'unnamed'}\` changed: ${reason || 'no reason given'}\n` +
|
|
319
|
-
`Agents may never change a test setup file${setup_files.length > 0 ? ` (${setup_files.join(', ')})` : ''}. Make the change yourself in the ticket's worktree, then reply retry.`
|
|
6
|
+
import { reasonLine } from './ticket-stuck-text'
|
|
7
|
+
|
|
8
|
+
import type { ReplyProblem } from '../journal/journal-record'
|
|
9
|
+
import type { ReplayedStuck } from '../journal/replay'
|
|
10
|
+
|
|
11
|
+
export { finalStuckComment } from './final-stuck-text'
|
|
12
|
+
export {
|
|
13
|
+
pullRequestStuckComment,
|
|
14
|
+
runBudgetDetail,
|
|
15
|
+
runStuckComment,
|
|
16
|
+
} from './run-stuck-text'
|
|
17
|
+
export {
|
|
18
|
+
badTestDetail,
|
|
19
|
+
badTestTwiceText,
|
|
20
|
+
setupChangeDetail,
|
|
21
|
+
} from './stuck-detail-text'
|
|
22
|
+
export {
|
|
23
|
+
reasonLine,
|
|
24
|
+
retrySection,
|
|
25
|
+
stuckComment,
|
|
26
|
+
triedText,
|
|
27
|
+
} from './ticket-stuck-text'
|
|
320
28
|
|
|
321
29
|
/** The comment a skipped ticket gets on its own issue. */
|
|
322
30
|
export const skipComment = ({
|
|
@@ -356,20 +64,9 @@ export const replyAnswer = ({
|
|
|
356
64
|
return '`ship` is only for a stuck final review. For a stuck ticket, reply `retry #n`, `skip #n`, or `stop`.'
|
|
357
65
|
case 'skip_not_for_final_review':
|
|
358
66
|
return "The final review can't be skipped. Reply `retry`, `stop`, or `ship` (opens the PR with the open findings listed at the top)."
|
|
67
|
+
case 'amount_only_for_run_budget':
|
|
68
|
+
return 'An amount after `retry` is only for a run stuck on its budget. Reply a bare `retry` here instead.'
|
|
69
|
+
case 'bad_amount':
|
|
70
|
+
return "Luca can't read that amount. Reply a bare `retry` to add half a run budget, or name a whole number of tokens above zero, such as `retry +500k` or `retry +3M`."
|
|
359
71
|
}
|
|
360
72
|
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* The prompt section for the first fresh agent after a `retry` resumed a
|
|
364
|
-
* stuck ticket: why it got stuck, and to keep the owner's edits.
|
|
365
|
-
*/
|
|
366
|
-
export const retrySection = ({ retried }: { retried: ReplayedStuck }): string =>
|
|
367
|
-
[
|
|
368
|
-
'## This ticket was retried',
|
|
369
|
-
'',
|
|
370
|
-
`It got stuck (${reasonLine({ reason: retried.reason })}) and the spec's owner replied \`retry\`. You are a fresh agent picking up where it stopped. The owner may have changed files in this worktree: keep their changes.`,
|
|
371
|
-
'',
|
|
372
|
-
'What stopped it:',
|
|
373
|
-
'',
|
|
374
|
-
fenced(retried.detail),
|
|
375
|
-
].join('\n')
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The texts about a stuck ticket: why it is stuck, what was tried, the stuck
|
|
3
|
+
* comment with its suggestion, and the retry note in a fresh agent's prompt.
|
|
4
|
+
*/
|
|
5
|
+
import compact from 'lodash/compact'
|
|
6
|
+
|
|
7
|
+
import { fenced, plural } from './stuck-format'
|
|
8
|
+
|
|
9
|
+
import type { StuckReason } from '../journal/journal-record'
|
|
10
|
+
import type {
|
|
11
|
+
ReplayedStuck,
|
|
12
|
+
ReplayedWorktree,
|
|
13
|
+
TicketProgress,
|
|
14
|
+
} from '../journal/replay'
|
|
15
|
+
import { REFACTOR_LABEL } from '../tracker/tracker'
|
|
16
|
+
|
|
17
|
+
/** Why a ticket is stuck, in one line. */
|
|
18
|
+
const REASON_LINES: Record<StuckReason, string> = {
|
|
19
|
+
agent_failed: 'An agent failed on every try it had.',
|
|
20
|
+
red_check_failed:
|
|
21
|
+
"The red check still fails: the new tests don't fail the way they should.",
|
|
22
|
+
nothing_new_to_test:
|
|
23
|
+
'The test-writer says the ticket changes no behavior, so there is nothing new to test: it looks like a refactor.',
|
|
24
|
+
leftovers_found:
|
|
25
|
+
'The leftover scan found files that must not be committed.',
|
|
26
|
+
gates_failed: 'The checks still fail.',
|
|
27
|
+
bad_test:
|
|
28
|
+
'A test was sent back as bad (by the implementer, or because it checks nothing), and it cannot go back again.',
|
|
29
|
+
changes_requested: 'The ticket review still asks for changes.',
|
|
30
|
+
join_failed: 'The ticket still clashes with the run branch.',
|
|
31
|
+
join_gates_failed:
|
|
32
|
+
'The checks still fail once the ticket joins the run branch.',
|
|
33
|
+
install_failed: 'Installing the dependencies failed.',
|
|
34
|
+
prepare_failed:
|
|
35
|
+
"The repo's prepare command failed before any agent worked.",
|
|
36
|
+
setup_change_needed: 'An agent needs a change only you may make.',
|
|
37
|
+
crashed: 'The engine crashed in the same step, again and again.',
|
|
38
|
+
undo_refused:
|
|
39
|
+
'Undoing its join would have rewound the run branch over other work, so the run branch was left alone.',
|
|
40
|
+
environment_failed:
|
|
41
|
+
"An agent says a failure is not the ticket's fault (the machine or another ticket), but the rerun failed the same way, or its reruns ran out.",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* How to keep a flagged doc the repo wants, for a `leftovers_found` stuck
|
|
46
|
+
* (#520); `retry` is the reply that retries it.
|
|
47
|
+
*/
|
|
48
|
+
export const leftoverAllowHint = ({ retry }: { retry: string }): string =>
|
|
49
|
+
`If one is a doc the repo wants, add a pattern for it to \`leftover_allow\` in the repo's \`.luca/config.json\` instead, resume the run (only a resume reads the config again, a plain \`retry\` doesn't), then reply ${retry}.`
|
|
50
|
+
|
|
51
|
+
/** What the user could do, per reason; `n` is the ticket. */
|
|
52
|
+
const suggestion = ({
|
|
53
|
+
reason,
|
|
54
|
+
n,
|
|
55
|
+
}: {
|
|
56
|
+
reason: StuckReason
|
|
57
|
+
n: number
|
|
58
|
+
}): string => {
|
|
59
|
+
const retry = `\`retry #${n}\``
|
|
60
|
+
switch (reason) {
|
|
61
|
+
case 'agent_failed':
|
|
62
|
+
return `Read the last error, fix what trips the agent (in the ticket or its worktree), then reply ${retry}.`
|
|
63
|
+
case 'red_check_failed':
|
|
64
|
+
return `Make the ticket's criteria clearer, or fix the tests in the worktree yourself, then reply ${retry}.`
|
|
65
|
+
case 'nothing_new_to_test':
|
|
66
|
+
return `A ticket that changes no behavior is a refactor ticket: add the \`${REFACTOR_LABEL}\` label, then reply ${retry}. It is built again as a refactor, with no new tests.`
|
|
67
|
+
case 'leftovers_found':
|
|
68
|
+
return `Delete those files in the worktree (or move them out of it), then reply ${retry}. ${leftoverAllowHint({ retry })}`
|
|
69
|
+
case 'gates_failed':
|
|
70
|
+
return `Fix the failing check in the worktree, or make the ticket clearer, then reply ${retry}.`
|
|
71
|
+
case 'bad_test':
|
|
72
|
+
return `Fix or remove that test in the worktree, or make the ticket clearer, then reply ${retry}.`
|
|
73
|
+
case 'changes_requested':
|
|
74
|
+
return `Fix the open findings in the worktree, then reply ${retry}; or reply \`skip #${n}\` if they can wait.`
|
|
75
|
+
case 'join_failed':
|
|
76
|
+
return `Reply ${retry} to put it on the run branch's tip again (uncommitted edits in its worktree are replaced), or \`skip #${n}\`.`
|
|
77
|
+
case 'join_gates_failed':
|
|
78
|
+
return `Its join was undone, so the run branch is safe. Reply ${retry} to join again and fix it on top of the run branch, or \`skip #${n}\`.`
|
|
79
|
+
case 'install_failed':
|
|
80
|
+
return `Fix the manifest or lockfile on the base branch, then reply ${retry}.`
|
|
81
|
+
case 'prepare_failed':
|
|
82
|
+
return `Fix what the prepare command needs on this computer (a missing tool, say), or the command in \`.luca/config.json\`, then reply ${retry}. If it ran out of time, raise \`prepare_timeout_ms\` there (30 minutes by default).`
|
|
83
|
+
case 'setup_change_needed':
|
|
84
|
+
return `Make that change yourself in the worktree, then reply ${retry}.`
|
|
85
|
+
case 'crashed':
|
|
86
|
+
return `Read the engine's log for why it crashed, fix what trips it, then reply ${retry} to take the step again with a fresh agent.`
|
|
87
|
+
case 'undo_refused':
|
|
88
|
+
return `Put the run branch back so this ticket's join is on top (or take its join off by hand), then reply ${retry}; or reply \`skip #${n}\`.`
|
|
89
|
+
case 'environment_failed':
|
|
90
|
+
return `Read the agent's evidence and the output. If the machine is at fault (a port in use, another process, a test that fails on the base branch too), fix that, then reply ${retry}. If the ticket's code is at fault, fix it in the worktree or make the ticket clearer, then reply ${retry}; or reply \`skip #${n}\`.`
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** What the engine already tried on a ticket, from its counts. */
|
|
95
|
+
export const triedText = ({
|
|
96
|
+
progress,
|
|
97
|
+
}: {
|
|
98
|
+
progress: TicketProgress
|
|
99
|
+
}): string => {
|
|
100
|
+
const tries = Object.entries(progress.failed_tries).flatMap(
|
|
101
|
+
([role, count]) =>
|
|
102
|
+
count === undefined || count === 0
|
|
103
|
+
? []
|
|
104
|
+
: [`${plural(count, 'failed try')} by the ${role}`]
|
|
105
|
+
)
|
|
106
|
+
const reviewFixRounds =
|
|
107
|
+
progress.review_fix === null
|
|
108
|
+
? 0
|
|
109
|
+
: Math.max(0, progress.review_fix.round - 1)
|
|
110
|
+
const parts = compact([
|
|
111
|
+
progress.red_fix_rounds > 0 &&
|
|
112
|
+
`${plural(progress.red_fix_rounds, 'fix round')} on the red check`,
|
|
113
|
+
progress.gate_fix_rounds > 0 &&
|
|
114
|
+
`${plural(progress.gate_fix_rounds, 'fix round')} on the checks`,
|
|
115
|
+
progress.bad_test_bounces > 0 &&
|
|
116
|
+
`${plural(progress.bad_test_bounces, 'bad test')} sent back`,
|
|
117
|
+
reviewFixRounds > 0 && plural(reviewFixRounds, 'review fix round'),
|
|
118
|
+
progress.rejoins > 0 &&
|
|
119
|
+
`${plural(progress.rejoins, 'rebase')} onto the run branch`,
|
|
120
|
+
progress.test_updates > 0 &&
|
|
121
|
+
`${plural(progress.test_updates, 'test update')} after a rebase`,
|
|
122
|
+
progress.engine_failures > 0 &&
|
|
123
|
+
`${plural(progress.engine_failures, 'engine failure')} in a row`,
|
|
124
|
+
...tries,
|
|
125
|
+
])
|
|
126
|
+
return parts.length === 0
|
|
127
|
+
? 'Nothing more: another try would fail the same way.'
|
|
128
|
+
: `${parts.join(', ')}.`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Why a ticket is stuck, in one line. */
|
|
132
|
+
export const reasonLine = ({ reason }: { reason: StuckReason }): string =>
|
|
133
|
+
REASON_LINES[reason]
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The comment on the spec issue when a ticket is stuck: which ticket, why
|
|
137
|
+
* (one line), what was tried, the last error, a suggestion, and the replies.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* stuckComment({ ticket: 11, title: 'Add sum', progress, stuck })
|
|
141
|
+
* // '**Ticket #11 is stuck: Add sum**\n\nWhy: The checks still fail. ...'
|
|
142
|
+
*/
|
|
143
|
+
export const stuckComment = ({
|
|
144
|
+
ticket,
|
|
145
|
+
title,
|
|
146
|
+
progress,
|
|
147
|
+
stuck,
|
|
148
|
+
worktree,
|
|
149
|
+
}: {
|
|
150
|
+
ticket: number
|
|
151
|
+
title: string
|
|
152
|
+
progress: TicketProgress
|
|
153
|
+
stuck: ReplayedStuck
|
|
154
|
+
worktree: ReplayedWorktree | null
|
|
155
|
+
}): string =>
|
|
156
|
+
compact([
|
|
157
|
+
`**Ticket #${ticket} is stuck: ${title}**`,
|
|
158
|
+
[
|
|
159
|
+
`Why: ${reasonLine({ reason: stuck.reason })}`,
|
|
160
|
+
`Tried: ${triedText({ progress })}`,
|
|
161
|
+
`Suggestion: ${suggestion({ reason: stuck.reason, n: ticket })}`,
|
|
162
|
+
worktree === null
|
|
163
|
+
? ''
|
|
164
|
+
: `Worktree: \`${worktree.path}\` (a \`retry\` keeps your code edits there)`,
|
|
165
|
+
]
|
|
166
|
+
.filter(Boolean)
|
|
167
|
+
.join('\n'),
|
|
168
|
+
`Last error:\n\n${fenced(stuck.detail)}`,
|
|
169
|
+
[
|
|
170
|
+
'Reply with one word on this issue:',
|
|
171
|
+
`- \`retry #${ticket}\`: pick up where it stopped with a fresh agent, or start it over if you changed the ticket's text or labels.`,
|
|
172
|
+
`- \`skip #${ticket}\`: leave it out, with the tickets that wait on it, and ship the rest. They stay open.`,
|
|
173
|
+
'- `stop`: end the run without a PR. The branch is kept.',
|
|
174
|
+
'',
|
|
175
|
+
'Only the spec owner counts. With one ticket stuck, the bare word works too. Other tickets keep building meanwhile.',
|
|
176
|
+
].join('\n'),
|
|
177
|
+
]).join('\n\n')
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The prompt section for the first fresh agent after a `retry` resumed a
|
|
181
|
+
* stuck ticket: why it got stuck, and to keep the owner's edits.
|
|
182
|
+
*/
|
|
183
|
+
export const retrySection = ({ retried }: { retried: ReplayedStuck }): string =>
|
|
184
|
+
[
|
|
185
|
+
'## This ticket was retried',
|
|
186
|
+
'',
|
|
187
|
+
`It got stuck (${reasonLine({ reason: retried.reason })}) and the spec's owner replied \`retry\`. You are a fresh agent picking up where it stopped. The owner may have changed files in this worktree: keep their changes.`,
|
|
188
|
+
'',
|
|
189
|
+
'What stopped it:',
|
|
190
|
+
'',
|
|
191
|
+
fenced(retried.detail),
|
|
192
|
+
].join('\n')
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a step counts as a worktree's changes: it notes what the prepare
|
|
3
|
+
* command made (`watchPrepare`), leaves that and outside links out
|
|
4
|
+
* (`leaveOutIn`), and lists the rest (`ticketChanges`).
|
|
5
|
+
*/
|
|
6
|
+
import difference from 'lodash/difference'
|
|
7
|
+
|
|
8
|
+
import type { BuildContext } from './build-context'
|
|
9
|
+
|
|
10
|
+
import type { FileChange } from '../git/git-adapter'
|
|
11
|
+
import { outsideLinks } from '../git/outside-links'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Runs `run`, a run of the config's prepare command in `cwd`, and journals
|
|
15
|
+
* as `prepare_made` the untracked paths it made: there after it ran, not
|
|
16
|
+
* before, and not known yet. Without a prepare command it only runs `run`.
|
|
17
|
+
*
|
|
18
|
+
* A crash in the middle of a prepare run leaves what it made so far; the
|
|
19
|
+
* redo's own "before" then holds those paths, so they count as the
|
|
20
|
+
* ticket's unless an earlier prepare run already noted them.
|
|
21
|
+
*/
|
|
22
|
+
export const watchPrepare = async <Result>({
|
|
23
|
+
context,
|
|
24
|
+
cwd,
|
|
25
|
+
ticket,
|
|
26
|
+
run,
|
|
27
|
+
}: {
|
|
28
|
+
context: BuildContext
|
|
29
|
+
cwd: string
|
|
30
|
+
/** The step's ticket, `null` in the final review. */
|
|
31
|
+
ticket: number | null
|
|
32
|
+
run: () => Promise<Result>
|
|
33
|
+
}): Promise<Result> => {
|
|
34
|
+
if (context.config.prepare === undefined) return run()
|
|
35
|
+
const before = await context.git.untracked({ cwd })
|
|
36
|
+
const result = await run()
|
|
37
|
+
const paths = difference(
|
|
38
|
+
await context.git.untracked({ cwd }),
|
|
39
|
+
before,
|
|
40
|
+
context.state.prepare_made
|
|
41
|
+
)
|
|
42
|
+
if (paths.length > 0) {
|
|
43
|
+
context.journal.append({
|
|
44
|
+
kind: 'prepare_made',
|
|
45
|
+
ticket,
|
|
46
|
+
role: null,
|
|
47
|
+
content: { paths },
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
return result
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The paths the engine leaves out of the changes at `cwd`: what prepare
|
|
55
|
+
* made (`prepare_made`), and every untracked symlink pointing outside the
|
|
56
|
+
* checkout, which no commit may hold (#496). Such a link not noted yet is
|
|
57
|
+
* most likely a prepare run's from before the engine noted what prepare
|
|
58
|
+
* made (an older engine's run, resumed); it is journaled as
|
|
59
|
+
* `prepare_made { paths, outside_links: true }`, so every later step leaves
|
|
60
|
+
* it out too.
|
|
61
|
+
*
|
|
62
|
+
* @param ticket - The step's ticket, `null` on the run branch.
|
|
63
|
+
* @returns Every path to leave out, the new links included.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* const leave_out = await leaveOutIn({ context, cwd, ticket: 11 })
|
|
67
|
+
* await context.git.commitAll({ cwd, message, leave_out })
|
|
68
|
+
*/
|
|
69
|
+
export const leaveOutIn = async ({
|
|
70
|
+
context,
|
|
71
|
+
cwd,
|
|
72
|
+
ticket,
|
|
73
|
+
}: {
|
|
74
|
+
context: BuildContext
|
|
75
|
+
cwd: string
|
|
76
|
+
ticket: number | null
|
|
77
|
+
}): Promise<string[]> => {
|
|
78
|
+
const { prepare_made } = context.state
|
|
79
|
+
const links = await outsideLinks({
|
|
80
|
+
cwd,
|
|
81
|
+
paths: difference(await context.git.untracked({ cwd }), prepare_made),
|
|
82
|
+
})
|
|
83
|
+
if (links.length > 0) {
|
|
84
|
+
context.journal.append({
|
|
85
|
+
kind: 'prepare_made',
|
|
86
|
+
ticket,
|
|
87
|
+
role: null,
|
|
88
|
+
content: { paths: links, outside_links: true },
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
return [...prepare_made, ...links]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The changes at `cwd`, less the paths in `leave_out`. */
|
|
95
|
+
export const ticketChanges = async ({
|
|
96
|
+
context,
|
|
97
|
+
cwd,
|
|
98
|
+
leave_out,
|
|
99
|
+
}: {
|
|
100
|
+
context: BuildContext
|
|
101
|
+
cwd: string
|
|
102
|
+
leave_out: string[]
|
|
103
|
+
}): Promise<FileChange[]> =>
|
|
104
|
+
(await context.git.changes({ cwd })).filter(
|
|
105
|
+
({ path }) => !leave_out.includes(path)
|
|
106
|
+
)
|