@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,350 @@
|
|
|
1
|
+
import type { BoardRecord } from './board-vocabulary'
|
|
2
|
+
import {
|
|
3
|
+
clip,
|
|
4
|
+
countFindings,
|
|
5
|
+
numbersText,
|
|
6
|
+
reviewCountsText,
|
|
7
|
+
startKind,
|
|
8
|
+
} from './reduce-board'
|
|
9
|
+
|
|
10
|
+
import type { Tone } from '../shared/board-rows'
|
|
11
|
+
import { LOOP_CAP, type BoardState } from '../shared/board-state'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The words for single records that need more than a line: an agent's start
|
|
15
|
+
* and finish (a ticket's, the final review's, the learner's), a message, a
|
|
16
|
+
* memory search or save, and why a reply was ignored. Pure.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** What a commit's `stage` committed, in words. */
|
|
20
|
+
export const COMMITTED: Record<string, string> = {
|
|
21
|
+
red: 'tests',
|
|
22
|
+
green: 'code',
|
|
23
|
+
fix: 'review fixes',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const on = ({ ticket }: { ticket: number | null }): string =>
|
|
27
|
+
ticket === null ? '' : `#${ticket}: `
|
|
28
|
+
|
|
29
|
+
export const event = ({ text, tone }: { text: string; tone: Tone }) => ({
|
|
30
|
+
text,
|
|
31
|
+
tone,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const lowerFirst = ({ text }: { text: string }): string =>
|
|
35
|
+
text.replace(/^./, (first) => first.toLowerCase())
|
|
36
|
+
|
|
37
|
+
/** Why the engine couldn't use a reply (`reply_ignored`'s `reason`), in words. */
|
|
38
|
+
export const ignoredText = ({
|
|
39
|
+
reason,
|
|
40
|
+
before,
|
|
41
|
+
}: {
|
|
42
|
+
reason: string
|
|
43
|
+
before: BoardState
|
|
44
|
+
}): string => {
|
|
45
|
+
switch (reason) {
|
|
46
|
+
case 'no_ticket_named': {
|
|
47
|
+
const stuck = before.needs_you.find((item) => item.ticket !== null)
|
|
48
|
+
return `name the ticket, like \`retry #${stuck?.ticket ?? 'n'}\``
|
|
49
|
+
}
|
|
50
|
+
case 'not_stuck':
|
|
51
|
+
return "the ticket it names isn't stuck"
|
|
52
|
+
case 'nothing_stuck':
|
|
53
|
+
return 'nothing is stuck right now'
|
|
54
|
+
case 'ship_needs_final_review':
|
|
55
|
+
return '`ship` is only for a stuck final review'
|
|
56
|
+
case 'amount_only_for_run_budget':
|
|
57
|
+
return 'an amount is only for a run stuck on its budget'
|
|
58
|
+
case 'bad_amount':
|
|
59
|
+
return 'the amount is not a whole number above zero'
|
|
60
|
+
default:
|
|
61
|
+
return reason.replaceAll('_', ' ')
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Why a run ended with nothing to do: its tickets were already done (#484),
|
|
67
|
+
* or what is open is for a person or waits on one (#499), or the spec has
|
|
68
|
+
* no open tickets.
|
|
69
|
+
*/
|
|
70
|
+
export const nothingToDoText = ({
|
|
71
|
+
content,
|
|
72
|
+
}: {
|
|
73
|
+
content: Extract<BoardRecord, { kind: 'nothing_to_do' }>['content']
|
|
74
|
+
}): string => {
|
|
75
|
+
const numbersOf = (reason: 'for_a_person' | 'waits_on_person') =>
|
|
76
|
+
content.left_out
|
|
77
|
+
.filter((ticket) => ticket.reason === reason)
|
|
78
|
+
.map(({ number }) => number)
|
|
79
|
+
const people = numbersOf('for_a_person')
|
|
80
|
+
const waiting = numbersOf('waits_on_person')
|
|
81
|
+
const done = content.already_done
|
|
82
|
+
const parts = [
|
|
83
|
+
done.length === 0
|
|
84
|
+
? ''
|
|
85
|
+
: `the work of ${done.map((number) => `#${number}`).join(', ')} was already on the base branch`,
|
|
86
|
+
people.length === 0
|
|
87
|
+
? ''
|
|
88
|
+
: `${numbersText({ numbers: people })} ${people.length === 1 ? 'is' : 'are'} for a person`,
|
|
89
|
+
waiting.length === 0
|
|
90
|
+
? ''
|
|
91
|
+
: `${numbersText({ numbers: waiting })} ${waiting.length === 1 ? 'waits' : 'wait'} on a ticket for a person`,
|
|
92
|
+
].filter((part) => part !== '')
|
|
93
|
+
if (parts.length === 0)
|
|
94
|
+
return 'Nothing to do: the spec has no open tickets.'
|
|
95
|
+
const last = parts.at(-1) ?? ''
|
|
96
|
+
const text =
|
|
97
|
+
parts.length === 1
|
|
98
|
+
? last
|
|
99
|
+
: `${parts.slice(0, -1).join(', ')}, and ${last}`
|
|
100
|
+
return `Nothing to do: ${text}.`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Longest message line shown in a row; the whole message is in the journal. */
|
|
104
|
+
const MESSAGE_LINE_MAX = 120
|
|
105
|
+
|
|
106
|
+
export const messageText = ({
|
|
107
|
+
record,
|
|
108
|
+
}: {
|
|
109
|
+
record: Extract<BoardRecord, { kind: 'agent_message' }>
|
|
110
|
+
}): { text: string; tone: Tone } => {
|
|
111
|
+
const { from, to, text, status, reason } = record.content
|
|
112
|
+
const why = reason ?? 'no reason given'
|
|
113
|
+
if (status === 'refused') {
|
|
114
|
+
return event({
|
|
115
|
+
text: `${from}'s message to ${to} was refused: ${why}`,
|
|
116
|
+
tone: 'warning',
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
const shown = clip({
|
|
120
|
+
text: text.split('\n')[0] ?? '',
|
|
121
|
+
max: MESSAGE_LINE_MAX,
|
|
122
|
+
})
|
|
123
|
+
return status === 'queued'
|
|
124
|
+
? event({ text: `${from} → ${to}: ${shown}`, tone: 'info' })
|
|
125
|
+
: event({
|
|
126
|
+
text: `${from} → ${to}: ${shown} (not delivered: ${why})`,
|
|
127
|
+
tone: 'warning',
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const RECALL_POINTS: Record<string, string> = {
|
|
132
|
+
run_start: "the run's start",
|
|
133
|
+
ticket: 'a ticket',
|
|
134
|
+
review: 'a review',
|
|
135
|
+
fix_round: 'a fix round',
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A memory search (#370) adds a row only when a vault failed, since the
|
|
140
|
+
* run goes on without it; the panel counts the rest.
|
|
141
|
+
*/
|
|
142
|
+
export const recalledText = ({
|
|
143
|
+
record,
|
|
144
|
+
}: {
|
|
145
|
+
record: Extract<BoardRecord, { kind: 'memory_recalled' }>
|
|
146
|
+
}): { text: string; tone: Tone } | null => {
|
|
147
|
+
const failed = record.content.vaults.filter(({ ok }) => !ok)
|
|
148
|
+
if (failed.length === 0) return null
|
|
149
|
+
const where = RECALL_POINTS[record.content.point] ?? record.content.point
|
|
150
|
+
const at = on({ ticket: record.ticket })
|
|
151
|
+
return event({
|
|
152
|
+
text: `${at}memory search for ${where} failed in ${failed.map(({ vault }) => vault).join(', ')}: ${failed[0]?.error?.split('\n')[0] ?? 'no reason given'} The run goes on.`,
|
|
153
|
+
tone: 'warning',
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The learner's answer (#370): how many memories it proposed, and helped. */
|
|
158
|
+
export const learnerFinishedText = ({
|
|
159
|
+
record,
|
|
160
|
+
}: {
|
|
161
|
+
record: Extract<BoardRecord, { kind: 'agent_finished' }>
|
|
162
|
+
}): { text: string; tone: Tone } => {
|
|
163
|
+
const proposed = record.content.result.memories?.length ?? 0
|
|
164
|
+
const helped = record.content.result.helped?.length ?? 0
|
|
165
|
+
return event({
|
|
166
|
+
text: `The learner proposed ${proposed} ${proposed === 1 ? 'memory' : 'memories'}; ${helped} shown ${helped === 1 ? 'memory' : 'memories'} helped.`,
|
|
167
|
+
tone: 'info',
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** What became of the learner's memories (#370), counted. */
|
|
172
|
+
export const savedText = ({
|
|
173
|
+
record,
|
|
174
|
+
}: {
|
|
175
|
+
record: Extract<BoardRecord, { kind: 'memories_saved' }>
|
|
176
|
+
}): { text: string; tone: Tone } => {
|
|
177
|
+
const count = (outcome: string) =>
|
|
178
|
+
record.content.saves.filter((save) => save.outcome === outcome).length
|
|
179
|
+
const failed = count('failed')
|
|
180
|
+
return event({
|
|
181
|
+
text: `Memories saved: ${count('added')} added, ${count('updated')} updated, ${count('refused')} refused, ${failed} failed.`,
|
|
182
|
+
tone: failed > 0 ? 'warning' : 'success',
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** A `ship` reply to the stuck final review, in words. */
|
|
187
|
+
export const SHIPPED_TEXT =
|
|
188
|
+
'You replied `ship`: the PR opens with the open findings listed at the top.'
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A final review agent's start (no ticket). A lens's start adds no row (its
|
|
192
|
+
* `lens_finished` says what it found); a fixer's says what it is fixing.
|
|
193
|
+
*/
|
|
194
|
+
export const finalStartedText = ({
|
|
195
|
+
record,
|
|
196
|
+
}: {
|
|
197
|
+
record: Extract<BoardRecord, { kind: 'agent_started' }>
|
|
198
|
+
}): { text: string; tone: Tone } | null => {
|
|
199
|
+
const { role, follow_up_of } = record.content
|
|
200
|
+
if (role.endsWith('-lens')) return null
|
|
201
|
+
return follow_up_of === null
|
|
202
|
+
? event({
|
|
203
|
+
text: `Final review: a fresh ${role} fixes the lenses' findings on the whole run branch.`,
|
|
204
|
+
tone: 'warning',
|
|
205
|
+
})
|
|
206
|
+
: event({
|
|
207
|
+
text: `Final review: the ${role} got the failure back.`,
|
|
208
|
+
tone: 'warning',
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* A fixer's answers counted: fixed, won't fix, and passed to the other
|
|
214
|
+
* writer role (`other_role`, #616), the last only when there are any.
|
|
215
|
+
*/
|
|
216
|
+
const answerCounts = ({
|
|
217
|
+
responses,
|
|
218
|
+
}: {
|
|
219
|
+
responses: { response: 'fixed' | 'wont_fix' | 'other_role' }[]
|
|
220
|
+
}): string => {
|
|
221
|
+
const count = (answer: string) =>
|
|
222
|
+
responses.filter(({ response }) => response === answer).length
|
|
223
|
+
const passed = count('other_role')
|
|
224
|
+
const counts = `${count('fixed')} fixed, ${count('wont_fix')} won't fix`
|
|
225
|
+
return passed === 0 ? counts : `${counts}, ${passed} for the other role`
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** A final review agent's finish (no ticket): a fixer's answers, or none for a lens. */
|
|
229
|
+
export const finalFinishedText = ({
|
|
230
|
+
record,
|
|
231
|
+
}: {
|
|
232
|
+
record: Extract<BoardRecord, { kind: 'agent_finished' }>
|
|
233
|
+
}): { text: string; tone: Tone } | null => {
|
|
234
|
+
const { role, result } = record.content
|
|
235
|
+
if (role.endsWith('-lens')) return null
|
|
236
|
+
const responses = result.finding_responses
|
|
237
|
+
if (responses.length === 0) {
|
|
238
|
+
return event({
|
|
239
|
+
text: `Final review: the ${role} finished.`,
|
|
240
|
+
tone: 'info',
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
return event({
|
|
244
|
+
text: `Final review: the ${role} answered the findings: ${answerCounts({ responses })}.`,
|
|
245
|
+
tone: 'info',
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export const startedText = ({
|
|
250
|
+
before,
|
|
251
|
+
after,
|
|
252
|
+
record,
|
|
253
|
+
}: {
|
|
254
|
+
before: BoardState
|
|
255
|
+
after: BoardState
|
|
256
|
+
record: Extract<BoardRecord, { kind: 'agent_started' }>
|
|
257
|
+
}): { text: string; tone: Tone } | null => {
|
|
258
|
+
const at = on({ ticket: record.ticket })
|
|
259
|
+
const { role, follow_up_of } = record.content
|
|
260
|
+
const card = before.tickets.find(({ number }) => number === record.ticket)
|
|
261
|
+
const next = after.tickets.find(({ number }) => number === record.ticket)
|
|
262
|
+
const round = next?.fix_round
|
|
263
|
+
switch (startKind({ card, role, follow_up_of })) {
|
|
264
|
+
case 'review_fix':
|
|
265
|
+
return event({
|
|
266
|
+
text: `${at}review fix round ${next?.review_fix_round ?? 1}/${LOOP_CAP}: ${
|
|
267
|
+
role === 'test-writer'
|
|
268
|
+
? 'a fresh test-writer fixes the test findings.'
|
|
269
|
+
: `the ${role} got the findings back.`
|
|
270
|
+
}`,
|
|
271
|
+
tone: 'warning',
|
|
272
|
+
})
|
|
273
|
+
case 'fix':
|
|
274
|
+
return event({
|
|
275
|
+
text: `${at}fix round ${round ?? 0}/${LOOP_CAP}: the ${role} got the failure back.`,
|
|
276
|
+
tone: 'warning',
|
|
277
|
+
})
|
|
278
|
+
case 'resent':
|
|
279
|
+
return null
|
|
280
|
+
case 'retry':
|
|
281
|
+
return event({
|
|
282
|
+
text: `${at}the ${role} tries again.`,
|
|
283
|
+
tone: 'warning',
|
|
284
|
+
})
|
|
285
|
+
case 'fresh':
|
|
286
|
+
return role === 'ticket-reviewer' &&
|
|
287
|
+
card !== undefined &&
|
|
288
|
+
card.review_fix_round > 0
|
|
289
|
+
? event({
|
|
290
|
+
text: `${at}re-review ${next?.review_round ?? 0} of the new changes.`,
|
|
291
|
+
tone: 'info',
|
|
292
|
+
})
|
|
293
|
+
: event({ text: `${at}the ${role} started.`, tone: 'info' })
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export const finishedText = ({
|
|
298
|
+
before,
|
|
299
|
+
record,
|
|
300
|
+
}: {
|
|
301
|
+
before: BoardState
|
|
302
|
+
record: Extract<BoardRecord, { kind: 'agent_finished' }>
|
|
303
|
+
}): { text: string; tone: Tone } => {
|
|
304
|
+
const at = on({ ticket: record.ticket })
|
|
305
|
+
const { role, result } = record.content
|
|
306
|
+
const card = before.tickets.find(({ number }) => number === record.ticket)
|
|
307
|
+
if (card?.open_check === 'review' && role !== 'ticket-reviewer') {
|
|
308
|
+
return event({
|
|
309
|
+
text: `${at}the ${role} answered the findings: ${answerCounts({ responses: result.finding_responses })}.`,
|
|
310
|
+
tone: 'info',
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
if (role === 'test-writer') {
|
|
314
|
+
if (result.outcome === 'already_done') {
|
|
315
|
+
return event({
|
|
316
|
+
text: `${at}the test-writer found the work already done.`,
|
|
317
|
+
tone: 'info',
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
return result.outcome === 'nothing_new_to_test'
|
|
321
|
+
? event({ text: `${at}nothing new to test.`, tone: 'info' })
|
|
322
|
+
: event({ text: `${at}tests written.`, tone: 'info' })
|
|
323
|
+
}
|
|
324
|
+
if (role === 'implementer') {
|
|
325
|
+
return result.outcome === 'bad_test'
|
|
326
|
+
? event({
|
|
327
|
+
text: `${at}the implementer sent back a bad test.`,
|
|
328
|
+
tone: 'warning',
|
|
329
|
+
})
|
|
330
|
+
: event({ text: `${at}code written.`, tone: 'info' })
|
|
331
|
+
}
|
|
332
|
+
if (role === 'ticket-reviewer') {
|
|
333
|
+
const counts = result.findings
|
|
334
|
+
? reviewCountsText({
|
|
335
|
+
findings: countFindings({ findings: result.findings }),
|
|
336
|
+
nits: true,
|
|
337
|
+
})
|
|
338
|
+
: null
|
|
339
|
+
return result.verdict === 'approve'
|
|
340
|
+
? event({
|
|
341
|
+
text: `${at}the ticket review approved it${counts ? ` (${counts})` : ''}.`,
|
|
342
|
+
tone: 'success',
|
|
343
|
+
})
|
|
344
|
+
: event({
|
|
345
|
+
text: `${at}the ticket review asked for changes${counts ? `: ${counts}` : ''}.`,
|
|
346
|
+
tone: 'warning',
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
return event({ text: `${at}the ${role} finished.`, tone: 'info' })
|
|
350
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An agent's start on a ticket: what kind of start it is (fresh, a retry, a
|
|
3
|
+
* fix round, a review fix round), the card's stage, step and activity for
|
|
4
|
+
* each role, and the model the agent runs on.
|
|
5
|
+
*/
|
|
6
|
+
import type { BoardRecord } from './board-vocabulary'
|
|
7
|
+
import { reviewCountsText } from './reduce-board-text'
|
|
8
|
+
import { updateTicket, withTried } from './reduce-board-update'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
LOOP_CAP,
|
|
12
|
+
type BoardState,
|
|
13
|
+
type TicketCard,
|
|
14
|
+
} from '../shared/board-state'
|
|
15
|
+
|
|
16
|
+
/** The roles that fix a ticket review's findings. */
|
|
17
|
+
const REVIEW_FIXERS = new Set(['test-writer', 'implementer'])
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* What an `agent_started` is, from the ticket's card before it:
|
|
21
|
+
* - `retry`: a new try after the role's (or any) turn failed;
|
|
22
|
+
* - `review_fix`: a test-writer (always fresh) or the implementer (a
|
|
23
|
+
* follow-up, or fresh if its session was lost) sent a ticket review's
|
|
24
|
+
* findings, in a review fix round;
|
|
25
|
+
* - `fix`: a follow-up answering a failed red check or gates, a new round;
|
|
26
|
+
* - `resent`: the same fix sent again (its turn never ended, such as after
|
|
27
|
+
* a crash);
|
|
28
|
+
* - `fresh`: anything else.
|
|
29
|
+
*/
|
|
30
|
+
export const startKind = ({
|
|
31
|
+
card,
|
|
32
|
+
role,
|
|
33
|
+
follow_up_of,
|
|
34
|
+
}: {
|
|
35
|
+
card: TicketCard | undefined
|
|
36
|
+
role: string
|
|
37
|
+
follow_up_of: string | null
|
|
38
|
+
}): 'retry' | 'review_fix' | 'fix' | 'resent' | 'fresh' => {
|
|
39
|
+
if (card === undefined) return 'fresh'
|
|
40
|
+
if (card.failed_turn !== null) return 'retry'
|
|
41
|
+
if (card.open_check === 'review' && REVIEW_FIXERS.has(role)) {
|
|
42
|
+
return card.role === null ? 'review_fix' : 'resent'
|
|
43
|
+
}
|
|
44
|
+
if (follow_up_of === null || card.open_check === null) return 'fresh'
|
|
45
|
+
return card.role === null ? 'fix' : 'resent'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const OPEN_CHECK_TEXT = {
|
|
49
|
+
red_check: 'red check',
|
|
50
|
+
gates: 'checks',
|
|
51
|
+
review: 'ticket review',
|
|
52
|
+
} as const
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A fixer's start in a review fix round. The round is the review's round (a
|
|
56
|
+
* round follows each review that asked for changes); only the round's first
|
|
57
|
+
* fixer adds its "tried" line. The card is fixing, at its step.
|
|
58
|
+
*/
|
|
59
|
+
const reviewFixStarted = ({
|
|
60
|
+
card,
|
|
61
|
+
role,
|
|
62
|
+
}: {
|
|
63
|
+
card: TicketCard
|
|
64
|
+
role: string
|
|
65
|
+
}): TicketCard => {
|
|
66
|
+
const round = Math.max(card.review_round, 1)
|
|
67
|
+
const counts = card.findings
|
|
68
|
+
? reviewCountsText({ findings: card.findings, nits: false })
|
|
69
|
+
: null
|
|
70
|
+
return {
|
|
71
|
+
...card,
|
|
72
|
+
started: true,
|
|
73
|
+
role,
|
|
74
|
+
stage: 'fixing',
|
|
75
|
+
review_fix_round: round,
|
|
76
|
+
activity: `fixing the review's findings (${round}/${LOOP_CAP})`,
|
|
77
|
+
tried:
|
|
78
|
+
card.review_fix_round < round
|
|
79
|
+
? withTried({
|
|
80
|
+
tried: card.tried,
|
|
81
|
+
line: `Review fix round ${round}/${LOOP_CAP}${counts ? `: ${counts}` : ''}`,
|
|
82
|
+
})
|
|
83
|
+
: card.tried,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The card's stage, step and activity once `role` starts on it. */
|
|
88
|
+
const startedCard = ({
|
|
89
|
+
card,
|
|
90
|
+
role,
|
|
91
|
+
follow_up_of,
|
|
92
|
+
}: {
|
|
93
|
+
card: TicketCard
|
|
94
|
+
role: string
|
|
95
|
+
follow_up_of: string | null
|
|
96
|
+
}): TicketCard => {
|
|
97
|
+
const kind = startKind({ card, role, follow_up_of })
|
|
98
|
+
if (
|
|
99
|
+
kind === 'review_fix' ||
|
|
100
|
+
(kind === 'resent' && card.open_check === 'review')
|
|
101
|
+
) {
|
|
102
|
+
return reviewFixStarted({ card, role })
|
|
103
|
+
}
|
|
104
|
+
const fix_round = kind === 'fix' ? card.fix_round + 1 : card.fix_round
|
|
105
|
+
const started = { ...card, started: true, role, fix_round }
|
|
106
|
+
const tried =
|
|
107
|
+
kind === 'fix' && card.open_check !== null
|
|
108
|
+
? withTried({
|
|
109
|
+
tried: card.tried,
|
|
110
|
+
line: `Fix round ${fix_round}/${LOOP_CAP} after the ${OPEN_CHECK_TEXT[card.open_check]}`,
|
|
111
|
+
})
|
|
112
|
+
: card.tried
|
|
113
|
+
const fixing = kind === 'fix' || kind === 'resent'
|
|
114
|
+
// A writer answering an open check, or fixing on top of the run
|
|
115
|
+
// branch, is fixing; its first pass is building.
|
|
116
|
+
const stage =
|
|
117
|
+
card.open_check !== null || card.stage === 'fixing'
|
|
118
|
+
? 'fixing'
|
|
119
|
+
: 'building'
|
|
120
|
+
if (role === 'test-writer') {
|
|
121
|
+
// A retried review fixer stays at its step.
|
|
122
|
+
return {
|
|
123
|
+
...started,
|
|
124
|
+
tried,
|
|
125
|
+
stage,
|
|
126
|
+
step: card.open_check === 'review' ? card.step : 0,
|
|
127
|
+
activity: fixing
|
|
128
|
+
? `fixing tests (${fix_round}/${LOOP_CAP})`
|
|
129
|
+
: kind === 'retry'
|
|
130
|
+
? 'writing tests again'
|
|
131
|
+
: 'writing tests',
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (role === 'implementer') {
|
|
135
|
+
return {
|
|
136
|
+
...started,
|
|
137
|
+
tried,
|
|
138
|
+
stage,
|
|
139
|
+
step: 2,
|
|
140
|
+
activity: fixing
|
|
141
|
+
? `fixing (${fix_round}/${LOOP_CAP})`
|
|
142
|
+
: kind === 'retry'
|
|
143
|
+
? 'coding again'
|
|
144
|
+
: 'coding',
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (role === 'ticket-reviewer') {
|
|
148
|
+
const retry = kind === 'retry'
|
|
149
|
+
return {
|
|
150
|
+
...started,
|
|
151
|
+
stage: 'reviewing',
|
|
152
|
+
step: 4,
|
|
153
|
+
open_check: retry ? card.open_check : null,
|
|
154
|
+
review_round: retry
|
|
155
|
+
? Math.max(card.review_round, 1)
|
|
156
|
+
: card.review_round + 1,
|
|
157
|
+
activity: retry
|
|
158
|
+
? 'reviewing again'
|
|
159
|
+
: card.review_fix_round > 0
|
|
160
|
+
? 're-reviewing'
|
|
161
|
+
: 'reviewing',
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { ...started, activity: role }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The card's model (#630): the agent's model and why, with its role's
|
|
169
|
+
* waiting `agent_model` answer, which it uses up. A follow-up asks Clef
|
|
170
|
+
* nothing new, so it keeps the role's last used answer. No model when the
|
|
171
|
+
* journal names none.
|
|
172
|
+
*/
|
|
173
|
+
const withModel = ({
|
|
174
|
+
card,
|
|
175
|
+
role,
|
|
176
|
+
follow_up_of,
|
|
177
|
+
model,
|
|
178
|
+
model_reason,
|
|
179
|
+
}: {
|
|
180
|
+
card: TicketCard
|
|
181
|
+
role: string
|
|
182
|
+
follow_up_of: string | null
|
|
183
|
+
model: string | null
|
|
184
|
+
model_reason: string | null
|
|
185
|
+
}): TicketCard => {
|
|
186
|
+
const waiting = card.model_answer?.role === role ? card.model_answer : null
|
|
187
|
+
const answer =
|
|
188
|
+
waiting ??
|
|
189
|
+
(follow_up_of === null ? null : (card.model_answers[role] ?? null))
|
|
190
|
+
return {
|
|
191
|
+
...card,
|
|
192
|
+
model:
|
|
193
|
+
model === null
|
|
194
|
+
? null
|
|
195
|
+
: { role, name: model, reason: model_reason, answer },
|
|
196
|
+
model_answer: waiting === null ? card.model_answer : null,
|
|
197
|
+
model_answers:
|
|
198
|
+
waiting === null
|
|
199
|
+
? card.model_answers
|
|
200
|
+
: { ...card.model_answers, [role]: waiting },
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const agentStarted = ({
|
|
205
|
+
state,
|
|
206
|
+
record,
|
|
207
|
+
}: {
|
|
208
|
+
state: BoardState
|
|
209
|
+
record: Extract<BoardRecord, { kind: 'agent_started' }>
|
|
210
|
+
}): BoardState => {
|
|
211
|
+
const { role, follow_up_of, model, model_reason } = record.content
|
|
212
|
+
return updateTicket({
|
|
213
|
+
state,
|
|
214
|
+
number: record.ticket,
|
|
215
|
+
update: (card) =>
|
|
216
|
+
withModel({
|
|
217
|
+
card: startedCard({ card, role, follow_up_of }),
|
|
218
|
+
role,
|
|
219
|
+
follow_up_of,
|
|
220
|
+
model,
|
|
221
|
+
model_reason,
|
|
222
|
+
}),
|
|
223
|
+
})
|
|
224
|
+
}
|