@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,296 @@
|
|
|
1
|
+
import { expect } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { createHarness, type Harness } from './testing/board-harness'
|
|
4
|
+
import {
|
|
5
|
+
agentFinished,
|
|
6
|
+
agentStarted,
|
|
7
|
+
baselineTests,
|
|
8
|
+
commitMade,
|
|
9
|
+
finding,
|
|
10
|
+
gatesRun,
|
|
11
|
+
intakeOfThree,
|
|
12
|
+
leftoverScan,
|
|
13
|
+
redCheck,
|
|
14
|
+
reviewFinished,
|
|
15
|
+
runBranchPushed,
|
|
16
|
+
sessionOf,
|
|
17
|
+
ticketJoined,
|
|
18
|
+
ticketWorktreeCreated,
|
|
19
|
+
type Entry,
|
|
20
|
+
} from './testing/journal-fixtures'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Helpers shared by the ticket stage tests (`ticket-stages-panel.test.ts`
|
|
24
|
+
* and the `ticket-stages-panel-*.test.ts` files beside it).
|
|
25
|
+
*
|
|
26
|
+
* The harness is the one the running test started; `runWith` sets it, and
|
|
27
|
+
* each test file cleans it up after every test.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export let harness: Harness
|
|
31
|
+
export let sent = { run_id: '', token: '', next_seq: 1 }
|
|
32
|
+
|
|
33
|
+
/** Starts a run and sends intake plus `entries` in one go. */
|
|
34
|
+
export const runWith = async ({ entries }: { entries: Entry[] }) => {
|
|
35
|
+
harness = await createHarness()
|
|
36
|
+
const { run_id, token } = await harness.start()
|
|
37
|
+
const reply = await harness.send({
|
|
38
|
+
run_id,
|
|
39
|
+
token,
|
|
40
|
+
entries: [...intakeOfThree(), ...entries],
|
|
41
|
+
})
|
|
42
|
+
expect(reply.ok).toBe(true)
|
|
43
|
+
sent = { run_id, token, next_seq: reply.next_seq }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Sends more records to the same run, after the ones already sent. */
|
|
47
|
+
export const sendMore = async ({ entries }: { entries: Entry[] }) => {
|
|
48
|
+
const reply = await harness.send({
|
|
49
|
+
run_id: sent.run_id,
|
|
50
|
+
token: sent.token,
|
|
51
|
+
entries,
|
|
52
|
+
first_seq: sent.next_seq,
|
|
53
|
+
})
|
|
54
|
+
expect(reply.ok).toBe(true)
|
|
55
|
+
sent = { ...sent, next_seq: reply.next_seq }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* `step_started` as the engine journals it: `key` is the ticket's number,
|
|
60
|
+
* or `run` for a step of no ticket; `step` is the action type, plus
|
|
61
|
+
* `:<role>` for an agent's turn.
|
|
62
|
+
*/
|
|
63
|
+
export const stepStarted = ({
|
|
64
|
+
ticket,
|
|
65
|
+
step,
|
|
66
|
+
role = null,
|
|
67
|
+
}: {
|
|
68
|
+
ticket: number | null
|
|
69
|
+
step: string
|
|
70
|
+
role?: string | null
|
|
71
|
+
}): Entry => ({
|
|
72
|
+
kind: 'step_started',
|
|
73
|
+
ticket,
|
|
74
|
+
role,
|
|
75
|
+
content: {
|
|
76
|
+
key: ticket === null ? 'run' : String(ticket),
|
|
77
|
+
step,
|
|
78
|
+
first_seq: null,
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
/** `step_ended` as the engine journals it once the step settles. */
|
|
83
|
+
export const stepEnded = ({
|
|
84
|
+
ticket,
|
|
85
|
+
step,
|
|
86
|
+
role = null,
|
|
87
|
+
}: {
|
|
88
|
+
ticket: number | null
|
|
89
|
+
step: string
|
|
90
|
+
role?: string | null
|
|
91
|
+
}): Entry => ({
|
|
92
|
+
kind: 'step_ended',
|
|
93
|
+
ticket,
|
|
94
|
+
role,
|
|
95
|
+
content: { key: ticket === null ? 'run' : String(ticket), step },
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
/** A whole step: its start, the records it wrote, and its end. */
|
|
99
|
+
export const wholeStep = ({
|
|
100
|
+
ticket,
|
|
101
|
+
step,
|
|
102
|
+
role = null,
|
|
103
|
+
entries,
|
|
104
|
+
}: {
|
|
105
|
+
ticket: number | null
|
|
106
|
+
step: string
|
|
107
|
+
role?: string | null
|
|
108
|
+
entries: Entry[]
|
|
109
|
+
}): Entry[] => [
|
|
110
|
+
stepStarted({ ticket, step, role }),
|
|
111
|
+
...entries,
|
|
112
|
+
stepEnded({ ticket, step, role }),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
/** An agent's turn starting: its step, then its `agent_started`. */
|
|
116
|
+
export const agentTurnStarts = ({
|
|
117
|
+
ticket,
|
|
118
|
+
role,
|
|
119
|
+
follow_up = false,
|
|
120
|
+
}: {
|
|
121
|
+
ticket: number
|
|
122
|
+
role: string
|
|
123
|
+
follow_up?: boolean
|
|
124
|
+
}): Entry[] => [
|
|
125
|
+
stepStarted({
|
|
126
|
+
ticket,
|
|
127
|
+
step: `${follow_up ? 'follow_up_agent' : 'launch_agent'}:${role}`,
|
|
128
|
+
role,
|
|
129
|
+
}),
|
|
130
|
+
agentStarted({
|
|
131
|
+
ticket,
|
|
132
|
+
role,
|
|
133
|
+
follow_up_of: follow_up ? sessionOf({ ticket, role }) : null,
|
|
134
|
+
}),
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
/** An agent's turn ending: its result, then its step's end. */
|
|
138
|
+
export const agentTurnEnds = ({
|
|
139
|
+
ticket,
|
|
140
|
+
role,
|
|
141
|
+
follow_up = false,
|
|
142
|
+
finished,
|
|
143
|
+
}: {
|
|
144
|
+
ticket: number
|
|
145
|
+
role: string
|
|
146
|
+
follow_up?: boolean
|
|
147
|
+
finished?: Entry
|
|
148
|
+
}): Entry[] => [
|
|
149
|
+
finished ?? agentFinished({ ticket, role }),
|
|
150
|
+
stepEnded({
|
|
151
|
+
ticket,
|
|
152
|
+
step: `${follow_up ? 'follow_up_agent' : 'launch_agent'}:${role}`,
|
|
153
|
+
role,
|
|
154
|
+
}),
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
/** A commit of `stage`, with its leftover scan, as one step. */
|
|
158
|
+
export const committed = ({
|
|
159
|
+
ticket,
|
|
160
|
+
stage,
|
|
161
|
+
}: {
|
|
162
|
+
ticket: number
|
|
163
|
+
stage: 'red' | 'green' | 'fix'
|
|
164
|
+
}): Entry[] =>
|
|
165
|
+
wholeStep({
|
|
166
|
+
ticket,
|
|
167
|
+
step: 'commit_ticket',
|
|
168
|
+
entries: [
|
|
169
|
+
leftoverScan({ ticket, stage }),
|
|
170
|
+
commitMade({ ticket, stage }),
|
|
171
|
+
],
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
/** The checks running on the ticket's worktree (or the run branch). */
|
|
175
|
+
export const gatesStart = ({ ticket }: { ticket: number }): Entry =>
|
|
176
|
+
stepStarted({ ticket, step: 'run_gates' })
|
|
177
|
+
|
|
178
|
+
export const gatesEnd = ({
|
|
179
|
+
ticket,
|
|
180
|
+
ok,
|
|
181
|
+
target = 'ticket',
|
|
182
|
+
}: {
|
|
183
|
+
ticket: number
|
|
184
|
+
ok: boolean
|
|
185
|
+
target?: 'ticket' | 'run_branch'
|
|
186
|
+
}): Entry[] => [
|
|
187
|
+
gatesRun({ ticket, ok, target }),
|
|
188
|
+
stepEnded({ ticket, step: 'run_gates' }),
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
export const blockerOnly = [finding({ id: 'R1-1', severity: 'blocker' })]
|
|
192
|
+
|
|
193
|
+
/** One point in a ticket's life: the records up to it, and its stage then. */
|
|
194
|
+
export type Checkpoint = { at: string; entries: Entry[]; stage: string }
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Sends each checkpoint's records in order and reads the ticket's stage
|
|
198
|
+
* after each, as `<at>: <stage>` lines, so a wrong stage names its point.
|
|
199
|
+
*/
|
|
200
|
+
export const stagesAlong = async ({
|
|
201
|
+
ticket,
|
|
202
|
+
checkpoints,
|
|
203
|
+
}: {
|
|
204
|
+
ticket: number
|
|
205
|
+
checkpoints: Checkpoint[]
|
|
206
|
+
}): Promise<string[]> => {
|
|
207
|
+
const seen: string[] = []
|
|
208
|
+
for (const { at, entries } of checkpoints) {
|
|
209
|
+
await sendMore({ entries })
|
|
210
|
+
seen.push(`${at}: ${(await harness.ticket({ number: ticket })).stage}`)
|
|
211
|
+
}
|
|
212
|
+
return seen
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export const expectedAlong = ({
|
|
216
|
+
checkpoints,
|
|
217
|
+
}: {
|
|
218
|
+
checkpoints: Checkpoint[]
|
|
219
|
+
}): string[] => checkpoints.map(({ at, stage }) => `${at}: ${stage}`)
|
|
220
|
+
|
|
221
|
+
export const stageOf = async ({ ticket }: { ticket: number }) =>
|
|
222
|
+
(await harness.ticket({ number: ticket })).stage
|
|
223
|
+
|
|
224
|
+
/** Ticket `ticket` from its worktree to its tests committed. */
|
|
225
|
+
export const testsCommitted = ({ ticket }: { ticket: number }): Entry[] => [
|
|
226
|
+
ticketWorktreeCreated({ ticket }),
|
|
227
|
+
...wholeStep({
|
|
228
|
+
ticket,
|
|
229
|
+
step: 'run_baseline_tests',
|
|
230
|
+
entries: [baselineTests({ ticket, passed: 4 })],
|
|
231
|
+
}),
|
|
232
|
+
...agentTurnStarts({ ticket, role: 'test-writer' }),
|
|
233
|
+
...agentTurnEnds({ ticket, role: 'test-writer' }),
|
|
234
|
+
...wholeStep({
|
|
235
|
+
ticket,
|
|
236
|
+
step: 'run_red_check',
|
|
237
|
+
entries: [redCheck({ ticket, ok: true, failing: 2, passing: 4 })],
|
|
238
|
+
}),
|
|
239
|
+
...committed({ ticket, stage: 'red' }),
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
/** Ticket `ticket` up to its first ticket review asking for changes. */
|
|
243
|
+
export const reviewedOnce = ({ ticket }: { ticket: number }): Entry[] => [
|
|
244
|
+
...testsCommitted({ ticket }),
|
|
245
|
+
...agentTurnStarts({ ticket, role: 'implementer' }),
|
|
246
|
+
...agentTurnEnds({ ticket, role: 'implementer' }),
|
|
247
|
+
gatesStart({ ticket }),
|
|
248
|
+
...gatesEnd({ ticket, ok: true }),
|
|
249
|
+
...committed({ ticket, stage: 'green' }),
|
|
250
|
+
...agentTurnStarts({ ticket, role: 'ticket-reviewer' }),
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
/** Ticket `ticket` approved by its ticket review, not yet joined. */
|
|
254
|
+
export const approved = ({ ticket }: { ticket: number }): Entry[] => [
|
|
255
|
+
...reviewedOnce({ ticket }),
|
|
256
|
+
...agentTurnEnds({
|
|
257
|
+
ticket,
|
|
258
|
+
role: 'ticket-reviewer',
|
|
259
|
+
finished: reviewFinished({ ticket }),
|
|
260
|
+
}),
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
/** An approved ticket's join, its gates on the run branch, and the push. */
|
|
264
|
+
export const joinedAndPushed = ({ ticket }: { ticket: number }): Entry[] => [
|
|
265
|
+
...wholeStep({
|
|
266
|
+
ticket,
|
|
267
|
+
step: 'join_run_branch',
|
|
268
|
+
entries: [ticketJoined({ ticket })],
|
|
269
|
+
}),
|
|
270
|
+
gatesStart({ ticket }),
|
|
271
|
+
...gatesEnd({ ticket, ok: true, target: 'run_branch' }),
|
|
272
|
+
...wholeStep({
|
|
273
|
+
ticket,
|
|
274
|
+
step: 'push_run_branch',
|
|
275
|
+
entries: [runBranchPushed({ ticket })],
|
|
276
|
+
}),
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
/** The refactor ticket 11: no test-writer and no red check. */
|
|
280
|
+
export const refactorDone = (): Entry[] => [
|
|
281
|
+
ticketWorktreeCreated({ ticket: 11 }),
|
|
282
|
+
...agentTurnStarts({ ticket: 11, role: 'implementer' }),
|
|
283
|
+
...agentTurnEnds({ ticket: 11, role: 'implementer' }),
|
|
284
|
+
gatesStart({ ticket: 11 }),
|
|
285
|
+
...gatesEnd({ ticket: 11, ok: true }),
|
|
286
|
+
...committed({ ticket: 11, stage: 'green' }),
|
|
287
|
+
...agentTurnStarts({ ticket: 11, role: 'ticket-reviewer' }),
|
|
288
|
+
...agentTurnEnds({
|
|
289
|
+
ticket: 11,
|
|
290
|
+
role: 'ticket-reviewer',
|
|
291
|
+
finished: reviewFinished({ ticket: 11 }),
|
|
292
|
+
}),
|
|
293
|
+
...joinedAndPushed({ ticket: 11 }),
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
export const T = 13
|
|
@@ -85,6 +85,15 @@ export const StuckRowSchema = z.object({
|
|
|
85
85
|
tried: z.array(z.string()),
|
|
86
86
|
replies: z.array(z.string()),
|
|
87
87
|
resolution: z.string().nullable(),
|
|
88
|
+
/**
|
|
89
|
+
* The run and the stuck item, for the row's reply buttons and "Help me"
|
|
90
|
+
* (#503). Defaulted, so a row added before them still renders (without
|
|
91
|
+
* the buttons).
|
|
92
|
+
*/
|
|
93
|
+
run_id: z.string().nullable().default(null),
|
|
94
|
+
key: z.string().nullable().default(null),
|
|
95
|
+
ticket: z.number().int().nullable().default(null),
|
|
96
|
+
since: z.string().nullable().default(null),
|
|
88
97
|
})
|
|
89
98
|
|
|
90
99
|
export type StuckRow = z.infer<typeof StuckRowSchema>
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
EngineEndedSchema,
|
|
7
7
|
RunSummarySchema,
|
|
8
8
|
} from './board-state'
|
|
9
|
+
import { PostedReplySchema } from './reply-actions'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* The plugin's RPCs. Payload keys are snake_case (API convention).
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
* - `engine.event`: the engine sends its journal records to the board.
|
|
15
16
|
* - `board.read`: the side panel polls the runs of one workspace.
|
|
16
17
|
* - `board.version`: `luca doctor` asks which Luca version is loaded.
|
|
18
|
+
* - `reply.post`: a reply button on stuck work posts its word on the spec.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -105,6 +107,13 @@ export const BoardReadInputSchema = z.object({
|
|
|
105
107
|
export const BoardReadOutputSchema = z.object({
|
|
106
108
|
runs: z.array(RunSummarySchema),
|
|
107
109
|
selected: BoardStateSchema.nullable(),
|
|
110
|
+
/**
|
|
111
|
+
* The chat the selected run started in, for "Help me"; `null` for a run
|
|
112
|
+
* the plugin didn't start. Defaulted, so an older daemon's answer parses.
|
|
113
|
+
*/
|
|
114
|
+
chat_agent_id: z.string().nullable().default(null),
|
|
115
|
+
/** The replies the board posted for the selected run (#503). */
|
|
116
|
+
posted: z.array(PostedReplySchema).default([]),
|
|
108
117
|
})
|
|
109
118
|
|
|
110
119
|
export const boardReadRpc = defineRpc({
|
|
@@ -129,3 +138,36 @@ export const boardVersionRpc = defineRpc({
|
|
|
129
138
|
input: z.object({}),
|
|
130
139
|
output: BoardVersionOutputSchema,
|
|
131
140
|
})
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* API Request: post one reply on a run's spec issue (#503). `key` is the
|
|
144
|
+
* stuck item's (`ticket-<n>`, `final`, or `run`), and `reply` one of the
|
|
145
|
+
* words the engine takes for it, such as `retry #134`.
|
|
146
|
+
*/
|
|
147
|
+
export const ReplyPostInputSchema = z.object({
|
|
148
|
+
run_id: z.string().min(1),
|
|
149
|
+
key: z.string().min(1),
|
|
150
|
+
reply: z.string().min(1),
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* API Response: `posted` (it went on the spec now), `already_posted` (the
|
|
155
|
+
* same reply was posted for this item before; nothing was posted again),
|
|
156
|
+
* `refused` (the board won't post it, and says why), or `failed` (`gh`
|
|
157
|
+
* failed). `ok` is true for the first two.
|
|
158
|
+
*/
|
|
159
|
+
export const ReplyPostOutputSchema = z.object({
|
|
160
|
+
ok: z.boolean(),
|
|
161
|
+
status: z.enum(['posted', 'already_posted', 'refused', 'failed']),
|
|
162
|
+
message: z.string(),
|
|
163
|
+
posted: PostedReplySchema.nullable(),
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
export const replyPostRpc = defineRpc({
|
|
167
|
+
name: 'reply.post',
|
|
168
|
+
input: ReplyPostInputSchema,
|
|
169
|
+
output: ReplyPostOutputSchema,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
export type ReplyPostInput = z.infer<typeof ReplyPostInputSchema>
|
|
173
|
+
export type ReplyPostOutput = z.infer<typeof ReplyPostOutputSchema>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the board only counts: the decision model's questions, agent
|
|
3
|
+
* messages, and memory with its learner.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The decision model (Clef; Jev in older journals) in shadow mode: how often
|
|
9
|
+
* the engine asked it and what came back. The engine never acts on the
|
|
10
|
+
* answers, so the board only counts them.
|
|
11
|
+
*/
|
|
12
|
+
export const JevCountsSchema = z.object({
|
|
13
|
+
asked: z.number().int().min(0),
|
|
14
|
+
answered: z.number().int().min(0),
|
|
15
|
+
failed: z.number().int().min(0),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export type JevCounts = z.infer<typeof JevCountsSchema>
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Agent messages: `sent` counts the queued and the not delivered ones,
|
|
22
|
+
* `refused` the ones the engine turned down, and `delivered` the messages
|
|
23
|
+
* handed over, once per receiver (a message to `all` can count more than
|
|
24
|
+
* once). Messages happen inside a turn, so the board only counts them.
|
|
25
|
+
*/
|
|
26
|
+
export const MessageCountsSchema = z.object({
|
|
27
|
+
sent: z.number().int().min(0),
|
|
28
|
+
refused: z.number().int().min(0),
|
|
29
|
+
delivered: z.number().int().min(0),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export type MessageCounts = z.infer<typeof MessageCountsSchema>
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Where the learner stands at the end of a run (#370): `waiting` until it
|
|
36
|
+
* starts (a run without memory never starts it), `learning`, `learned`
|
|
37
|
+
* once it answered, or `skipped` when the engine gave up on it.
|
|
38
|
+
*/
|
|
39
|
+
export const LearnerStateSchema = z.enum([
|
|
40
|
+
'waiting',
|
|
41
|
+
'learning',
|
|
42
|
+
'learned',
|
|
43
|
+
'skipped',
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
export type LearnerState = z.infer<typeof LearnerStateSchema>
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Memory (#370), counted: the searches at recall points (and the vault
|
|
50
|
+
* searches among them that failed), the memories they showed agents, the
|
|
51
|
+
* learner, and what became of its memories. Memory changes no ticket, so
|
|
52
|
+
* the board only counts it.
|
|
53
|
+
*/
|
|
54
|
+
export const MemoryCountsSchema = z.object({
|
|
55
|
+
searches: z.number().int().min(0),
|
|
56
|
+
search_errors: z.number().int().min(0),
|
|
57
|
+
shown: z.number().int().min(0),
|
|
58
|
+
learner: LearnerStateSchema,
|
|
59
|
+
added: z.number().int().min(0),
|
|
60
|
+
updated: z.number().int().min(0),
|
|
61
|
+
refused: z.number().int().min(0),
|
|
62
|
+
failed: z.number().int().min(0),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export type MemoryCounts = z.infer<typeof MemoryCountsSchema>
|
|
66
|
+
|
|
67
|
+
/** Memory before any of its records. */
|
|
68
|
+
export const NO_MEMORY: MemoryCounts = {
|
|
69
|
+
searches: 0,
|
|
70
|
+
search_errors: 0,
|
|
71
|
+
shown: 0,
|
|
72
|
+
learner: 'waiting',
|
|
73
|
+
added: 0,
|
|
74
|
+
updated: 0,
|
|
75
|
+
refused: 0,
|
|
76
|
+
failed: 0,
|
|
77
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live file (#628) in the board's state: what an agent in a turn is
|
|
3
|
+
* doing now, from the engine's `live.json`, never from the journal.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A live file written longer ago than this means the engine is hung or
|
|
9
|
+
* gone (#635). The engine rewrites it at least every 5 seconds, so a long
|
|
10
|
+
* tool call never gets near it.
|
|
11
|
+
*/
|
|
12
|
+
export const LIVE_STALE_MS = 60_000
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether a live file written at `written_at` is fresh at `now`: written
|
|
16
|
+
* at most `LIVE_STALE_MS` ago.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* liveIsFresh({ written_at: '2026-09-23T12:30:00.000Z', now: Date.parse('2026-09-23T12:31:10.000Z') }) // false
|
|
20
|
+
*/
|
|
21
|
+
export const liveIsFresh = ({
|
|
22
|
+
written_at,
|
|
23
|
+
now,
|
|
24
|
+
}: {
|
|
25
|
+
written_at: string
|
|
26
|
+
now: number
|
|
27
|
+
}): boolean => now - Date.parse(written_at) <= LIVE_STALE_MS
|
|
28
|
+
|
|
29
|
+
/** The agent's latest action: a tool and a short target, with its time. */
|
|
30
|
+
export const LiveActionSchema = z.object({
|
|
31
|
+
tool: z.string(),
|
|
32
|
+
target: z.string(),
|
|
33
|
+
at: z.string(),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
/** A tool call still running, since `started_at`. */
|
|
37
|
+
export const LiveOpenCallSchema = z.object({
|
|
38
|
+
tool: z.string(),
|
|
39
|
+
target: z.string(),
|
|
40
|
+
started_at: z.string(),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* One of an agent's helpers (#636): a read-only Haiku session it started,
|
|
45
|
+
* with its latest action and whether it's done.
|
|
46
|
+
*/
|
|
47
|
+
export const LiveHelperSchema = z.object({
|
|
48
|
+
model: z.string().nullable().default(null),
|
|
49
|
+
action: LiveActionSchema.nullable().default(null),
|
|
50
|
+
done: z.boolean().default(false),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export type LiveHelper = z.infer<typeof LiveHelperSchema>
|
|
54
|
+
|
|
55
|
+
/** One agent in a turn, as the live file has it. */
|
|
56
|
+
export const LiveAgentSchema = z.object({
|
|
57
|
+
ticket: z.number().int().nullable(),
|
|
58
|
+
role: z.string(),
|
|
59
|
+
model: z.string().nullable().default(null),
|
|
60
|
+
/** The turn's tokens so far, cache reads included; the journal has none of them yet. */
|
|
61
|
+
tokens: z.number().min(0).default(0),
|
|
62
|
+
action: LiveActionSchema.nullable().default(null),
|
|
63
|
+
open_call: LiveOpenCallSchema.nullable().default(null),
|
|
64
|
+
/**
|
|
65
|
+
* The agent's activity count for each of the last 30 seconds (#631),
|
|
66
|
+
* oldest first; a file from before has none.
|
|
67
|
+
*/
|
|
68
|
+
activity: z.array(z.number().min(0)).catch([]).default([]),
|
|
69
|
+
/** The agent's helpers (#636), oldest first; a file from before has none. */
|
|
70
|
+
helpers: z.array(LiveHelperSchema).catch([]).default([]),
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
export type LiveAgent = z.infer<typeof LiveAgentSchema>
|
|
74
|
+
|
|
75
|
+
/** One action of the run's live feed (#634): when, who, on which model, and what. */
|
|
76
|
+
export const LiveFeedEntrySchema = z.object({
|
|
77
|
+
at: z.string(),
|
|
78
|
+
ticket: z.number().int().nullable(),
|
|
79
|
+
role: z.string(),
|
|
80
|
+
model: z.string().nullable().default(null),
|
|
81
|
+
tool: z.string(),
|
|
82
|
+
target: z.string(),
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
export type LiveFeedEntry = z.infer<typeof LiveFeedEntrySchema>
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A wait inside a step (#632): a ticket's check waits for a prepare slot
|
|
89
|
+
* or a test slot, while `in_use` of the engine's `slots` are taken.
|
|
90
|
+
*/
|
|
91
|
+
export const LiveWaitSchema = z.object({
|
|
92
|
+
ticket: z.number().int(),
|
|
93
|
+
for: z.enum(['prepare_slot', 'test_slot']),
|
|
94
|
+
in_use: z.number().int().min(0),
|
|
95
|
+
slots: z.number().int().min(0),
|
|
96
|
+
since: z.string(),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
export type LiveWait = z.infer<typeof LiveWaitSchema>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The final review in the board's state: its lenses, and where the review
|
|
3
|
+
* stands.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
import { FindingCountsSchema } from './board-state-ticket'
|
|
8
|
+
|
|
9
|
+
export const LENS_NAMES = [
|
|
10
|
+
'architecture',
|
|
11
|
+
'simplification',
|
|
12
|
+
'security',
|
|
13
|
+
'integration',
|
|
14
|
+
'rules',
|
|
15
|
+
] as const
|
|
16
|
+
|
|
17
|
+
export const LensNameSchema = z.enum(LENS_NAMES)
|
|
18
|
+
|
|
19
|
+
export type LensName = z.infer<typeof LensNameSchema>
|
|
20
|
+
|
|
21
|
+
export const LensStateSchema = z.enum([
|
|
22
|
+
'waiting',
|
|
23
|
+
'reviewing',
|
|
24
|
+
'fixing',
|
|
25
|
+
'clean',
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
export type LensState = z.infer<typeof LensStateSchema>
|
|
29
|
+
|
|
30
|
+
export const LensCardSchema = z.object({
|
|
31
|
+
name: LensNameSchema,
|
|
32
|
+
state: LensStateSchema,
|
|
33
|
+
findings: FindingCountsSchema,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export type LensCard = z.infer<typeof LensCardSchema>
|
|
37
|
+
|
|
38
|
+
export const FinalReviewStateSchema = z.enum([
|
|
39
|
+
'waiting',
|
|
40
|
+
'reviewing',
|
|
41
|
+
'fixing',
|
|
42
|
+
'stuck',
|
|
43
|
+
'passed',
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
export type FinalReviewState = z.infer<typeof FinalReviewStateSchema>
|
|
47
|
+
|
|
48
|
+
export const FinalReviewSchema = z.object({
|
|
49
|
+
state: FinalReviewStateSchema,
|
|
50
|
+
/** False (drawn dimmed) until every ticket is done or skipped. */
|
|
51
|
+
active: z.boolean(),
|
|
52
|
+
round: z.number().int().min(0),
|
|
53
|
+
fix_round: z.number().int().min(0),
|
|
54
|
+
lenses: z.array(LensCardSchema),
|
|
55
|
+
/** What was tried on it so far, oldest first. */
|
|
56
|
+
tried: z.array(z.string()),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export type FinalReview = z.infer<typeof FinalReviewSchema>
|