@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,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixtures shared by the run budget decision tests (#435, #543): a run with
|
|
3
|
+
* a budget in its engine config, agent turns that count their tokens per
|
|
4
|
+
* model, and the run stuck on its budget with the spec issue told.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
intakePassed,
|
|
8
|
+
practiceTicket,
|
|
9
|
+
runBranchCreated,
|
|
10
|
+
SESSIONS,
|
|
11
|
+
ticketBuilt,
|
|
12
|
+
withInstalls,
|
|
13
|
+
} from './build-fixtures'
|
|
14
|
+
import { recordsFrom } from './intake-fixtures'
|
|
15
|
+
|
|
16
|
+
import type { AgentRole } from '../agents/role-results'
|
|
17
|
+
import { decideSteps } from '../core/decide'
|
|
18
|
+
import type { TicketSnapshot } from '../intake/intake-schemas'
|
|
19
|
+
import type { JournalEntry } from '../journal/journal-record'
|
|
20
|
+
|
|
21
|
+
export const SUM = practiceTicket({ number: 11, title: 'Add sum' })
|
|
22
|
+
export const PRODUCT = practiceTicket({ number: 12, title: 'Add product' })
|
|
23
|
+
|
|
24
|
+
/** The budget the tests give the run in its engine config. */
|
|
25
|
+
export const BUDGET = 50_000
|
|
26
|
+
|
|
27
|
+
type Tokens = {
|
|
28
|
+
input_tokens: number
|
|
29
|
+
output_tokens: number
|
|
30
|
+
cache_read_input_tokens: number
|
|
31
|
+
cache_creation_input_tokens: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const tokens = (
|
|
35
|
+
input: number,
|
|
36
|
+
output: number,
|
|
37
|
+
cache_read: number,
|
|
38
|
+
cache_creation: number
|
|
39
|
+
): Tokens => ({
|
|
40
|
+
input_tokens: input,
|
|
41
|
+
output_tokens: output,
|
|
42
|
+
cache_read_input_tokens: cache_read,
|
|
43
|
+
cache_creation_input_tokens: cache_creation,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
export const OPUS = 'claude-opus-5-5'
|
|
47
|
+
export const HAIKU = 'claude-haiku-4-5-20251001'
|
|
48
|
+
|
|
49
|
+
/** One agent turn's session, with its tokens per model. */
|
|
50
|
+
export const turnSession = ({
|
|
51
|
+
ticket,
|
|
52
|
+
role,
|
|
53
|
+
model_usage,
|
|
54
|
+
}: {
|
|
55
|
+
ticket: number | null
|
|
56
|
+
role: AgentRole
|
|
57
|
+
model_usage: Record<string, Tokens>
|
|
58
|
+
}): JournalEntry => ({
|
|
59
|
+
kind: 'agent_session',
|
|
60
|
+
ticket,
|
|
61
|
+
role,
|
|
62
|
+
content: {
|
|
63
|
+
role,
|
|
64
|
+
session: {
|
|
65
|
+
session_id: SESSIONS[role],
|
|
66
|
+
usage: tokens(0, 0, 0, 0),
|
|
67
|
+
model_usage,
|
|
68
|
+
rate_limit_events: [],
|
|
69
|
+
billing_error: false,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
/** A turn that counts exactly `counted` tokens, all output, on Opus. */
|
|
75
|
+
export const turnOf = ({
|
|
76
|
+
ticket,
|
|
77
|
+
role,
|
|
78
|
+
counted,
|
|
79
|
+
}: {
|
|
80
|
+
ticket: number
|
|
81
|
+
role: AgentRole
|
|
82
|
+
counted: number
|
|
83
|
+
}): JournalEntry =>
|
|
84
|
+
turnSession({
|
|
85
|
+
ticket,
|
|
86
|
+
role,
|
|
87
|
+
model_usage: { [OPUS]: tokens(0, counted, 0, 0) },
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The decision step's actions for a run with these tickets and entries
|
|
92
|
+
* after intake. `budget` is the config's `run_budget_tokens`; leave it out
|
|
93
|
+
* for a config without one.
|
|
94
|
+
*/
|
|
95
|
+
export const stepsAfter = ({
|
|
96
|
+
tickets,
|
|
97
|
+
entries,
|
|
98
|
+
budget,
|
|
99
|
+
}: {
|
|
100
|
+
tickets: TicketSnapshot[]
|
|
101
|
+
entries: JournalEntry[]
|
|
102
|
+
budget?: number
|
|
103
|
+
}) =>
|
|
104
|
+
decideSteps({
|
|
105
|
+
records: recordsFrom({
|
|
106
|
+
entries: [
|
|
107
|
+
...intakePassed({ tickets }).map(
|
|
108
|
+
(entry): JournalEntry =>
|
|
109
|
+
entry.kind === 'run_started' && budget !== undefined
|
|
110
|
+
? {
|
|
111
|
+
...entry,
|
|
112
|
+
content: {
|
|
113
|
+
...entry.content,
|
|
114
|
+
config: {
|
|
115
|
+
...entry.content.config,
|
|
116
|
+
run_budget_tokens: budget,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
: entry
|
|
121
|
+
),
|
|
122
|
+
...withInstalls({ entries }),
|
|
123
|
+
],
|
|
124
|
+
}),
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
export const oneSteps = (entries: JournalEntry[], budget: number | undefined) =>
|
|
128
|
+
stepsAfter({ tickets: [SUM], entries, budget })
|
|
129
|
+
|
|
130
|
+
/** #11's worktree, baseline, and tests written: its red check is next. */
|
|
131
|
+
export const sumWritten = (): JournalEntry[] =>
|
|
132
|
+
ticketBuilt({ ticket: 11 }).slice(0, 3)
|
|
133
|
+
|
|
134
|
+
/** #11 with its tests written, and its test-writer's turn counting `counted`. */
|
|
135
|
+
export const sumSpent = (counted: number): JournalEntry[] => [
|
|
136
|
+
runBranchCreated(),
|
|
137
|
+
...sumWritten(),
|
|
138
|
+
turnOf({ ticket: 11, role: 'test-writer', counted }),
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
/** The run went stuck on its budget. */
|
|
142
|
+
export const runStuck = (): JournalEntry => ({
|
|
143
|
+
kind: 'run_stuck',
|
|
144
|
+
ticket: null,
|
|
145
|
+
role: null,
|
|
146
|
+
content: {
|
|
147
|
+
reason: 'run_budget',
|
|
148
|
+
detail: `The run used ${BUDGET} tokens of its ${BUDGET} budget.`,
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
/** The engine told the spec issue the run is stuck, in comment 150. */
|
|
153
|
+
export const runStuckReported = (): JournalEntry => ({
|
|
154
|
+
kind: 'stuck_reported',
|
|
155
|
+
ticket: null,
|
|
156
|
+
role: null,
|
|
157
|
+
content: { comment_id: 150, body: 'stuck' },
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
/** #11 used the whole budget; the run is stuck and the spec issue told. */
|
|
161
|
+
export const told = (): JournalEntry[] => [
|
|
162
|
+
...sumSpent(BUDGET),
|
|
163
|
+
runStuck(),
|
|
164
|
+
runStuckReported(),
|
|
165
|
+
]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal entry builders for a run's scaffolding: its config and tickets, its
|
|
3
|
+
* branch and worktrees, the installs and baseline tests, and the end of the
|
|
4
|
+
* run (worktrees removed, the PR opened).
|
|
5
|
+
*/
|
|
6
|
+
import { SPEC_OWNER } from './intake-fixtures'
|
|
7
|
+
|
|
8
|
+
import type { EngineConfig } from '../config/engine-config'
|
|
9
|
+
import type { TestRun } from '../gates/gate-schemas'
|
|
10
|
+
import type { TicketSnapshot } from '../intake/intake-schemas'
|
|
11
|
+
import type { JournalEntry } from '../journal/journal-record'
|
|
12
|
+
|
|
13
|
+
export const BUILD_CONFIG: EngineConfig = {
|
|
14
|
+
checks: { test: 'bun test', types: 'bun run types', lint: 'bun run lint' },
|
|
15
|
+
test_file_patterns: ['**/*.test.ts'],
|
|
16
|
+
test_setup_files: [],
|
|
17
|
+
rule_files: [],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A ticket as intake snapshots it. */
|
|
21
|
+
export const practiceTicket = ({
|
|
22
|
+
number,
|
|
23
|
+
title,
|
|
24
|
+
criteria,
|
|
25
|
+
labels,
|
|
26
|
+
blockers,
|
|
27
|
+
}: {
|
|
28
|
+
number: number
|
|
29
|
+
title?: string
|
|
30
|
+
criteria?: string[]
|
|
31
|
+
/** Defaults to `ready-for-agent` only; add `refactor` for a refactor ticket. */
|
|
32
|
+
labels?: string[]
|
|
33
|
+
/** The tickets it waits on. Defaults to none. */
|
|
34
|
+
blockers?: number[]
|
|
35
|
+
}): TicketSnapshot => ({
|
|
36
|
+
number,
|
|
37
|
+
title: title ?? 'Add sum',
|
|
38
|
+
body: '## What to build\n\nA sum function.',
|
|
39
|
+
labels: labels ?? ['ready-for-agent'],
|
|
40
|
+
url: `https://github.com/acme/app/issues/${number}`,
|
|
41
|
+
criteria: (criteria ?? ['sum adds two numbers']).map((text, index) => ({
|
|
42
|
+
id: `AC${index + 1}`,
|
|
43
|
+
text,
|
|
44
|
+
})),
|
|
45
|
+
blockers: blockers ?? [],
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
/** A run whose intake passed with these tickets, in this order. */
|
|
49
|
+
export const intakePassed = ({
|
|
50
|
+
tickets,
|
|
51
|
+
}: {
|
|
52
|
+
tickets: TicketSnapshot[]
|
|
53
|
+
}): JournalEntry[] => [
|
|
54
|
+
{
|
|
55
|
+
kind: 'run_started',
|
|
56
|
+
ticket: null,
|
|
57
|
+
role: null,
|
|
58
|
+
content: { spec_number: 10, config: BUILD_CONFIG, base_branch: 'main' },
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
kind: 'spec_snapshot',
|
|
62
|
+
ticket: 10,
|
|
63
|
+
role: null,
|
|
64
|
+
content: {
|
|
65
|
+
spec: {
|
|
66
|
+
number: 10,
|
|
67
|
+
title: 'Practice spec',
|
|
68
|
+
body: '## Testing Decisions\n\n- Test the sum.',
|
|
69
|
+
labels: ['ready-for-agent'],
|
|
70
|
+
url: 'https://github.com/acme/app/issues/10',
|
|
71
|
+
author: SPEC_OWNER,
|
|
72
|
+
},
|
|
73
|
+
ticket_order: tickets.map((ticket) => ticket.number),
|
|
74
|
+
closed_tickets: [],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
...tickets.map(
|
|
78
|
+
(ticket): JournalEntry => ({
|
|
79
|
+
kind: 'ticket_snapshot',
|
|
80
|
+
ticket: ticket.number,
|
|
81
|
+
role: null,
|
|
82
|
+
content: ticket,
|
|
83
|
+
})
|
|
84
|
+
),
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
export const RUN_BRANCH = 'luca/spec-10-run'
|
|
88
|
+
|
|
89
|
+
/** Where fixtures put each ticket's worktree, and the run branch's. */
|
|
90
|
+
export const ticketPath = (ticket: number): string =>
|
|
91
|
+
`/runs/run/tickets/${ticket}`
|
|
92
|
+
|
|
93
|
+
export const RUN_BRANCH_PATH = '/runs/run/run-branch'
|
|
94
|
+
|
|
95
|
+
export const runBranchCreated = (): JournalEntry => ({
|
|
96
|
+
kind: 'run_branch_created',
|
|
97
|
+
ticket: null,
|
|
98
|
+
role: null,
|
|
99
|
+
content: {
|
|
100
|
+
branch: RUN_BRANCH,
|
|
101
|
+
path: RUN_BRANCH_PATH,
|
|
102
|
+
base_sha: 'b0',
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
export const ticketWorktreeCreated = ({
|
|
107
|
+
ticket,
|
|
108
|
+
}: {
|
|
109
|
+
ticket: number
|
|
110
|
+
}): JournalEntry => ({
|
|
111
|
+
kind: 'ticket_worktree_created',
|
|
112
|
+
ticket,
|
|
113
|
+
role: null,
|
|
114
|
+
content: {
|
|
115
|
+
branch: `${RUN_BRANCH}--ticket-${ticket}`,
|
|
116
|
+
path: ticketPath(ticket),
|
|
117
|
+
base_sha: 'b0',
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The engine's install in a new worktree: the run branch's checkout
|
|
123
|
+
* (`ticket` unset) or a ticket's worktree. Passing unless `ok` is false.
|
|
124
|
+
*/
|
|
125
|
+
export const dependenciesInstalled = ({
|
|
126
|
+
ticket,
|
|
127
|
+
ok,
|
|
128
|
+
}: {
|
|
129
|
+
ticket?: number
|
|
130
|
+
ok?: boolean
|
|
131
|
+
}): JournalEntry => ({
|
|
132
|
+
kind: 'dependencies_installed',
|
|
133
|
+
ticket: ticket ?? null,
|
|
134
|
+
role: null,
|
|
135
|
+
content: {
|
|
136
|
+
target: ticket === undefined ? 'run_branch' : 'ticket',
|
|
137
|
+
check: {
|
|
138
|
+
name: 'install',
|
|
139
|
+
command: 'bun install --frozen-lockfile',
|
|
140
|
+
ok: ok ?? true,
|
|
141
|
+
exit_code: ok === false ? 1 : 0,
|
|
142
|
+
output:
|
|
143
|
+
ok === false
|
|
144
|
+
? 'error: lockfile had changes, but lockfile is frozen'
|
|
145
|
+
: '',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The entries with a passing install after each new worktree that has none,
|
|
152
|
+
* so tests about later steps need not list the installs.
|
|
153
|
+
*/
|
|
154
|
+
export const withInstalls = ({
|
|
155
|
+
entries,
|
|
156
|
+
}: {
|
|
157
|
+
entries: JournalEntry[]
|
|
158
|
+
}): JournalEntry[] =>
|
|
159
|
+
entries.flatMap((entry, index) => {
|
|
160
|
+
const made =
|
|
161
|
+
entry.kind === 'run_branch_created' ||
|
|
162
|
+
entry.kind === 'ticket_worktree_created'
|
|
163
|
+
if (!made || entries[index + 1]?.kind === 'dependencies_installed') {
|
|
164
|
+
return [entry]
|
|
165
|
+
}
|
|
166
|
+
return [
|
|
167
|
+
entry,
|
|
168
|
+
dependenciesInstalled({ ticket: entry.ticket ?? undefined }),
|
|
169
|
+
]
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
/** A test run with no test files, as a fresh repo has. */
|
|
173
|
+
export const emptyTestRun = (): TestRun => ({
|
|
174
|
+
command: 'bun test',
|
|
175
|
+
ok: true,
|
|
176
|
+
exit_code: 1,
|
|
177
|
+
no_test_files: true,
|
|
178
|
+
cases: [],
|
|
179
|
+
files_without_results: [],
|
|
180
|
+
output: '0 test files matching',
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
export const baselineTests = ({
|
|
184
|
+
ticket,
|
|
185
|
+
}: {
|
|
186
|
+
ticket: number
|
|
187
|
+
}): JournalEntry => ({
|
|
188
|
+
kind: 'baseline_tests',
|
|
189
|
+
ticket,
|
|
190
|
+
role: null,
|
|
191
|
+
content: emptyTestRun(),
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
/** The engine removed these worktrees at the end of the run. */
|
|
195
|
+
export const worktreesRemoved = ({
|
|
196
|
+
paths,
|
|
197
|
+
}: {
|
|
198
|
+
paths: string[]
|
|
199
|
+
}): JournalEntry => ({
|
|
200
|
+
kind: 'worktrees_removed',
|
|
201
|
+
ticket: null,
|
|
202
|
+
role: null,
|
|
203
|
+
content: { paths },
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
/** The run's one PR, #99, opened from the run branch. */
|
|
207
|
+
export const pullRequestOpened = (): JournalEntry => ({
|
|
208
|
+
kind: 'pull_request_opened',
|
|
209
|
+
ticket: null,
|
|
210
|
+
role: null,
|
|
211
|
+
content: {
|
|
212
|
+
number: 99,
|
|
213
|
+
url: 'https://github.com/acme/app/pull/99',
|
|
214
|
+
head: RUN_BRANCH,
|
|
215
|
+
base: 'main',
|
|
216
|
+
title: 'Practice spec (#10)',
|
|
217
|
+
body: '',
|
|
218
|
+
},
|
|
219
|
+
})
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
import type { HookJSONOutput, Options } from '@anthropic-ai/claude-agent-sdk'
|
|
4
|
+
|
|
5
|
+
import type { AgentQuery } from '../agents/claude-launcher'
|
|
6
|
+
import { fakeQuery, repo } from '../agents/claude-launcher-helpers'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A fake SDK session for the launcher's tests: SDK-shaped messages played
|
|
10
|
+
* through `createClaudeLauncher`'s `query`, and tool calls played through
|
|
11
|
+
* the guard hooks the launch set up, as the SDK would. No model, no process.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** A step: an SDK message, or work to do once the launcher has read the message before it. */
|
|
15
|
+
export type Step = unknown | ((options: Options) => Promise<void>)
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A fake `query` that plays `steps`: each message is yielded in turn, and
|
|
19
|
+
* each function runs after the launcher has read the message before it,
|
|
20
|
+
* the way the agent's tool calls happen mid-turn.
|
|
21
|
+
*/
|
|
22
|
+
export const playing = ({ steps }: { steps: Step[] }): AgentQuery => {
|
|
23
|
+
const messages: unknown[] = []
|
|
24
|
+
const after = new Map<unknown, ((options: Options) => Promise<void>)[]>()
|
|
25
|
+
for (const step of steps) {
|
|
26
|
+
if (typeof step === 'function') {
|
|
27
|
+
const last = messages.at(-1)
|
|
28
|
+
after.set(last, [
|
|
29
|
+
...(after.get(last) ?? []),
|
|
30
|
+
step as (options: Options) => Promise<void>,
|
|
31
|
+
])
|
|
32
|
+
} else messages.push(step)
|
|
33
|
+
}
|
|
34
|
+
const fake = fakeQuery({ messages })
|
|
35
|
+
return (params) => {
|
|
36
|
+
const inner = fake.query(params)
|
|
37
|
+
return {
|
|
38
|
+
...inner,
|
|
39
|
+
[Symbol.asyncIterator]: async function* () {
|
|
40
|
+
for await (const message of inner) {
|
|
41
|
+
yield message
|
|
42
|
+
for (const work of after.get(message) ?? []) {
|
|
43
|
+
await work(params.options)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** One message's token counts, as the SDK reports them. */
|
|
52
|
+
export type Usage = {
|
|
53
|
+
input_tokens: number
|
|
54
|
+
output_tokens: number
|
|
55
|
+
cache_read_input_tokens: number
|
|
56
|
+
cache_creation_input_tokens: number
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* One API response, as the SDK streams it: the agent's (`parent` null), or
|
|
61
|
+
* a helper's (`parent` the `Agent` call that started it).
|
|
62
|
+
*/
|
|
63
|
+
export const assistant = ({
|
|
64
|
+
id,
|
|
65
|
+
content,
|
|
66
|
+
parent = null,
|
|
67
|
+
model = 'claude-opus-5-5',
|
|
68
|
+
usage = {
|
|
69
|
+
input_tokens: 10,
|
|
70
|
+
output_tokens: 10,
|
|
71
|
+
cache_read_input_tokens: 0,
|
|
72
|
+
cache_creation_input_tokens: 0,
|
|
73
|
+
},
|
|
74
|
+
}: {
|
|
75
|
+
id: string
|
|
76
|
+
content: unknown[]
|
|
77
|
+
parent?: string | null
|
|
78
|
+
model?: string
|
|
79
|
+
usage?: Usage
|
|
80
|
+
}) => ({
|
|
81
|
+
type: 'assistant',
|
|
82
|
+
message: {
|
|
83
|
+
id,
|
|
84
|
+
type: 'message',
|
|
85
|
+
role: 'assistant',
|
|
86
|
+
model,
|
|
87
|
+
content,
|
|
88
|
+
stop_reason: 'tool_use',
|
|
89
|
+
usage,
|
|
90
|
+
},
|
|
91
|
+
parent_tool_use_id: parent,
|
|
92
|
+
session_id: 'session-1',
|
|
93
|
+
uuid: `uuid-${id}`,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
/** A tool call inside an `assistant` message's content. */
|
|
97
|
+
export const toolUse = (
|
|
98
|
+
id: string,
|
|
99
|
+
name: string,
|
|
100
|
+
input: Record<string, unknown>
|
|
101
|
+
) => ({ type: 'tool_use', id, name, input })
|
|
102
|
+
|
|
103
|
+
/** A tool's result, handed back to the agent (`parent` null) or a helper. */
|
|
104
|
+
export const toolResult = ({
|
|
105
|
+
tool_use_id,
|
|
106
|
+
content,
|
|
107
|
+
parent = null,
|
|
108
|
+
}: {
|
|
109
|
+
tool_use_id: string
|
|
110
|
+
content: string
|
|
111
|
+
parent?: string | null
|
|
112
|
+
}) => ({
|
|
113
|
+
type: 'user',
|
|
114
|
+
message: {
|
|
115
|
+
role: 'user',
|
|
116
|
+
content: [{ type: 'tool_result', tool_use_id, content }],
|
|
117
|
+
},
|
|
118
|
+
parent_tool_use_id: parent,
|
|
119
|
+
session_id: 'session-1',
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Plays one tool call through every `PreToolUse` hook the launch set up
|
|
124
|
+
* whose matcher fits the tool, as the SDK would, and gives the first
|
|
125
|
+
* denial, or `{}` when every hook let it through. With `helper`, the call
|
|
126
|
+
* comes from inside a helper (the SDK's `agent_id` and `agent_type`). `cwd`
|
|
127
|
+
* is the test's repo, its real path, unless told.
|
|
128
|
+
*/
|
|
129
|
+
export const preToolUse = async ({
|
|
130
|
+
options,
|
|
131
|
+
tool_name,
|
|
132
|
+
tool_input,
|
|
133
|
+
tool_use_id,
|
|
134
|
+
helper = false,
|
|
135
|
+
cwd = realpathSync(repo),
|
|
136
|
+
}: {
|
|
137
|
+
options: Options
|
|
138
|
+
tool_name: string
|
|
139
|
+
tool_input: Record<string, unknown>
|
|
140
|
+
tool_use_id: string
|
|
141
|
+
helper?: boolean
|
|
142
|
+
cwd?: string
|
|
143
|
+
}): Promise<HookJSONOutput> => {
|
|
144
|
+
const signal = new AbortController().signal
|
|
145
|
+
for (const matcher of options.hooks?.PreToolUse ?? []) {
|
|
146
|
+
if (
|
|
147
|
+
matcher.matcher !== undefined &&
|
|
148
|
+
!new RegExp(`^(?:${matcher.matcher})$`).test(tool_name)
|
|
149
|
+
)
|
|
150
|
+
continue
|
|
151
|
+
for (const hook of matcher.hooks) {
|
|
152
|
+
const answer = await hook(
|
|
153
|
+
{
|
|
154
|
+
hook_event_name: 'PreToolUse',
|
|
155
|
+
tool_name,
|
|
156
|
+
tool_input,
|
|
157
|
+
tool_use_id,
|
|
158
|
+
session_id: 'session-1',
|
|
159
|
+
transcript_path: '/dev/null',
|
|
160
|
+
cwd,
|
|
161
|
+
...(helper
|
|
162
|
+
? { agent_id: 'helper-1', agent_type: 'searcher' }
|
|
163
|
+
: {}),
|
|
164
|
+
},
|
|
165
|
+
tool_use_id,
|
|
166
|
+
{ signal }
|
|
167
|
+
)
|
|
168
|
+
if (
|
|
169
|
+
'hookSpecificOutput' in answer &&
|
|
170
|
+
answer.hookSpecificOutput?.hookEventName === 'PreToolUse' &&
|
|
171
|
+
answer.hookSpecificOutput.permissionDecision === 'deny'
|
|
172
|
+
)
|
|
173
|
+
return answer
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {}
|
|
177
|
+
}
|