@alecsibilia/luca 14.0.0-alpha.21 → 14.0.0-alpha.22

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.
@@ -0,0 +1,139 @@
1
+ import {
2
+ runOf,
3
+ ticketRun,
4
+ type Built,
5
+ type Effort,
6
+ type Severity,
7
+ } from './model-score-fixtures'
8
+
9
+ import { ModelsConfigSchema } from '../config/engine-config'
10
+ import type { JournalEntry, JournalRecord } from '../journal/journal-record'
11
+ import type { ModelReport } from '../models/model-score'
12
+
13
+ /**
14
+ * Journal builders for the score report's "Clef's effort picks" section
15
+ * (#663): Clef's shadow-mode `agent_effort` answers, the tickets they were
16
+ * asked on, and a run with its own confidence bar.
17
+ */
18
+
19
+ /** Clef's `agent_effort` answer about `role`. */
20
+ export type EffortPick = {
21
+ role: Built
22
+ pick: Effort
23
+ confidence: number | null
24
+ }
25
+
26
+ /**
27
+ * An `agent_effort` ask about `role`, in shadow mode, and Clef's answer.
28
+ * `runOf` points the answer's `asked_seq` at the ask.
29
+ */
30
+ export const effortAsk = ({
31
+ ticket,
32
+ role,
33
+ pick,
34
+ confidence,
35
+ }: EffortPick & { ticket: number }): JournalEntry[] => [
36
+ {
37
+ kind: 'jev_asked',
38
+ ticket,
39
+ role,
40
+ content: {
41
+ job: 'agent_effort',
42
+ request: {
43
+ state: {},
44
+ questions: {
45
+ effort: {
46
+ type: 'choice',
47
+ instructions:
48
+ 'Which effort should this agent use? Pick the lowest that will get it right.',
49
+ criteria: { medium: null, high: null, xhigh: null },
50
+ },
51
+ },
52
+ },
53
+ fixed: { effort: 'medium' },
54
+ acted: false,
55
+ },
56
+ },
57
+ {
58
+ kind: 'jev_answered',
59
+ ticket,
60
+ role,
61
+ content: {
62
+ job: 'agent_effort',
63
+ asked_seq: 1,
64
+ answers: { effort: { value: pick, confidence, raw: {} } },
65
+ ms: 5,
66
+ },
67
+ },
68
+ ]
69
+
70
+ /**
71
+ * One ticket after Clef's effort `picks`: the test-writer at `tw_effort`
72
+ * and the implementer at `impl_effort` (both medium unless told), every
73
+ * ticket review at xhigh, the first review's `findings`, 100 lines.
74
+ */
75
+ export const pickedTicket = ({
76
+ ticket,
77
+ tw_effort = 'medium',
78
+ impl_effort = 'medium',
79
+ picks = [],
80
+ findings = [],
81
+ }: {
82
+ ticket: number
83
+ tw_effort?: Effort
84
+ impl_effort?: Effort
85
+ picks?: EffortPick[]
86
+ findings?: Severity[]
87
+ }): JournalEntry[] => [
88
+ ...picks.flatMap((pick) => effortAsk({ ticket, ...pick })),
89
+ ...ticketRun({
90
+ ticket,
91
+ tw_effort,
92
+ impl_effort,
93
+ review_effort: 'xhigh',
94
+ findings,
95
+ }),
96
+ ]
97
+
98
+ /** A run of `body` whose config sets the confidence bar to `bar`. */
99
+ export const runWithBar = (
100
+ body: JournalEntry[],
101
+ bar: number
102
+ ): JournalRecord[] =>
103
+ runOf(body).map((record) =>
104
+ record.kind === 'run_started'
105
+ ? {
106
+ ...record,
107
+ content: {
108
+ ...record.content,
109
+ config: {
110
+ ...record.content.config,
111
+ models: ModelsConfigSchema.parse({
112
+ confidence_bar: bar,
113
+ }),
114
+ },
115
+ },
116
+ }
117
+ : record
118
+ )
119
+
120
+ /** The report's "Clef's effort picks" for `role`. */
121
+ export const effortPicksOf = (report: ModelReport, role: Built) =>
122
+ report.effort_picks.find((picks) => picks.role === role)
123
+
124
+ /**
125
+ * The lines of `role`'s "Clef's effort picks (shadow mode)" section in the
126
+ * printed report: the lines after its heading, within the role's part.
127
+ */
128
+ export const pickSection = (text: string, role: Built): string[] => {
129
+ const lines = text.split('\n')
130
+ const start = lines.indexOf(role)
131
+ const end = lines.findIndex(
132
+ (line, at) => at > start && line !== '' && !line.startsWith(' ')
133
+ )
134
+ const part = start < 0 ? [] : lines.slice(start + 1, end)
135
+ const heading = part.findIndex(
136
+ (line) => line.trim() === "Clef's effort picks (shadow mode)"
137
+ )
138
+ return heading < 0 ? [] : part.slice(heading + 1)
139
+ }
@@ -0,0 +1,300 @@
1
+ import {
2
+ agentFailed,
3
+ agentSession,
4
+ finding,
5
+ implemented,
6
+ intakePassed,
7
+ practiceTicket,
8
+ pushed,
9
+ reviewed,
10
+ testsWritten,
11
+ } from './build-fixtures'
12
+ import { recordsFrom } from './intake-fixtures'
13
+
14
+ import type { AgentRole } from '../agents/role-results'
15
+ import type { JournalEntry, JournalRecord } from '../journal/journal-record'
16
+ import type { ModelScore } from '../models/model-score'
17
+
18
+ /**
19
+ * Journal builders for the model score report's tests (#605): a ticket
20
+ * built with each role on its model and effort, a run's records, and
21
+ * lookups into the report's scores.
22
+ */
23
+
24
+ export type Model = 'opus' | 'sonnet' | 'haiku'
25
+ export type Effort = 'low' | 'medium' | 'high' | 'xhigh' | 'max'
26
+ export type Severity = 'blocker' | 'should_fix' | 'nit'
27
+ export type Built = 'test-writer' | 'implementer'
28
+
29
+ /**
30
+ * An agent turn started on `model`; `null` writes one from before #600. It
31
+ * ran at `effort`; left out writes one from before #659, with no effort.
32
+ */
33
+ export const started = ({
34
+ ticket,
35
+ role,
36
+ model,
37
+ follow_up_of,
38
+ effort,
39
+ }: {
40
+ ticket: number | null
41
+ role: AgentRole
42
+ model: Model | null
43
+ follow_up_of?: string
44
+ effort?: Effort
45
+ }): JournalEntry => ({
46
+ kind: 'agent_started',
47
+ ticket,
48
+ role,
49
+ content: {
50
+ role,
51
+ prompt: 'p',
52
+ follow_up_of: follow_up_of ?? null,
53
+ ...(model === null ? {} : { model }),
54
+ ...(effort === undefined ? {} : { effort }),
55
+ },
56
+ })
57
+
58
+ /**
59
+ * An `agent_model` ask about `role` and Clef's answer, or with `role` null
60
+ * an older journal's `ticket_model` ask, which named full model ids.
61
+ * `runOf` points the answer's `asked_seq` at the ask.
62
+ */
63
+ export const modelAsk = ({
64
+ ticket,
65
+ role,
66
+ pick,
67
+ confidence,
68
+ acted,
69
+ }: {
70
+ ticket: number | null
71
+ role: Built | 'learner' | null
72
+ pick: string
73
+ confidence: number | null
74
+ acted?: boolean
75
+ }): JournalEntry[] => {
76
+ const job = role === null ? 'ticket_model' : 'agent_model'
77
+ const options =
78
+ role === null
79
+ ? ['claude-opus-5-5', 'claude-sonnet-5', 'claude-haiku-4-5']
80
+ : ['opus', 'sonnet', 'haiku']
81
+ return [
82
+ {
83
+ kind: 'jev_asked',
84
+ ticket,
85
+ role,
86
+ content: {
87
+ job,
88
+ request: {
89
+ state: {},
90
+ questions: {
91
+ model: {
92
+ type: 'choice',
93
+ instructions: 'Which model?',
94
+ criteria: Object.fromEntries(
95
+ options.map((option) => [option, null])
96
+ ),
97
+ },
98
+ },
99
+ },
100
+ fixed: { model: options[0] ?? 'opus' },
101
+ ...(acted === undefined ? {} : { acted }),
102
+ },
103
+ },
104
+ {
105
+ kind: 'jev_answered',
106
+ ticket,
107
+ role,
108
+ content: {
109
+ job,
110
+ asked_seq: 1,
111
+ answers: { model: { value: pick, confidence, raw: {} } },
112
+ ms: 5,
113
+ },
114
+ },
115
+ ]
116
+ }
117
+
118
+ export type Ask = Omit<Parameters<typeof modelAsk>[0], 'ticket'>
119
+
120
+ /**
121
+ * One ticket built: the test-writer on `tw`, the implementer on `impl`
122
+ * (opus unless told), the first review's `findings`, then `rounds` fix
123
+ * rounds (one when the findings block), each fix's review finding a
124
+ * should-fix until the last approves, and a join of `lines` lines. Each
125
+ * role's agents run at its effort, left out as before #659.
126
+ */
127
+ export const ticketRun = ({
128
+ ticket,
129
+ tw = 'opus',
130
+ impl = 'opus',
131
+ tw_effort,
132
+ impl_effort,
133
+ review_effort,
134
+ asks = [],
135
+ findings = [],
136
+ rounds,
137
+ lines = 100,
138
+ tokens = 100,
139
+ tw_failed_first = false,
140
+ }: {
141
+ ticket: number
142
+ tw?: Model | null
143
+ impl?: Model | null
144
+ tw_effort?: Effort
145
+ impl_effort?: Effort
146
+ review_effort?: Effort
147
+ asks?: Ask[]
148
+ findings?: Severity[]
149
+ rounds?: number
150
+ /** `null` joins as an older journal does, with no `lines_changed`. */
151
+ lines?: number | null
152
+ /** The ticket's tokens, all in its test-writer's session. */
153
+ tokens?: number
154
+ /** The test-writer on `tw` fails a try; a fresh one on opus follows. */
155
+ tw_failed_first?: boolean
156
+ }): JournalEntry[] => {
157
+ const askFor = (role: Built | null) =>
158
+ asks
159
+ .filter((ask) => ask.role === role)
160
+ .flatMap((ask) => modelAsk({ ticket, ...ask }))
161
+ const fixes =
162
+ rounds ?? (findings.some((severity) => severity !== 'nit') ? 1 : 0)
163
+ const review = (list: Severity[], index: number): JournalEntry[] => [
164
+ started({
165
+ ticket,
166
+ role: 'ticket-reviewer',
167
+ model: 'opus',
168
+ effort: review_effort,
169
+ }),
170
+ reviewed({
171
+ ticket,
172
+ findings: list.map((severity, at) =>
173
+ finding({ id: `F${index}-${at}`, severity })
174
+ ),
175
+ }),
176
+ ]
177
+ return [
178
+ ...askFor(null),
179
+ ...askFor('test-writer'),
180
+ ...(tw_failed_first
181
+ ? [
182
+ started({
183
+ ticket,
184
+ role: 'test-writer',
185
+ model: tw,
186
+ effort: tw_effort,
187
+ }),
188
+ agentFailed({
189
+ ticket,
190
+ role: 'test-writer',
191
+ failure: 'agent',
192
+ }),
193
+ ]
194
+ : []),
195
+ started({
196
+ ticket,
197
+ role: 'test-writer',
198
+ model: tw_failed_first ? 'opus' : tw,
199
+ effort: tw_effort,
200
+ }),
201
+ testsWritten({ ticket }),
202
+ agentSession({
203
+ ticket,
204
+ role: 'test-writer',
205
+ output_tokens: tokens - 10,
206
+ }),
207
+ ...askFor('implementer'),
208
+ started({
209
+ ticket,
210
+ role: 'implementer',
211
+ model: impl,
212
+ effort: impl_effort,
213
+ }),
214
+ implemented({ ticket }),
215
+ ...review(findings, 0),
216
+ ...Array.from({ length: fixes }, (_, index) => [
217
+ started({
218
+ ticket,
219
+ role: 'implementer',
220
+ model: impl,
221
+ follow_up_of: 'impl-1',
222
+ effort: impl_effort,
223
+ }),
224
+ implemented({ ticket }),
225
+ ...review(index + 1 < fixes ? ['should_fix'] : [], index + 1),
226
+ ]).flat(),
227
+ {
228
+ kind: 'ticket_joined',
229
+ ticket,
230
+ role: null,
231
+ content: {
232
+ ok: true,
233
+ shas: ['r1', 'g1'],
234
+ ...(lines === null ? {} : { lines_changed: lines }),
235
+ },
236
+ },
237
+ pushed({ ticket }),
238
+ {
239
+ kind: 'usage_recorded',
240
+ ticket,
241
+ role: null,
242
+ content: {
243
+ scope: 'ticket',
244
+ ticket,
245
+ agent_turns: 1,
246
+ tokens: {
247
+ input_tokens: 10,
248
+ output_tokens: tokens - 10,
249
+ cache_read_input_tokens: 0,
250
+ cache_creation_input_tokens: 0,
251
+ },
252
+ windows: {},
253
+ },
254
+ },
255
+ ]
256
+ }
257
+
258
+ /** A run's records: its intake, then `body`, each answer after its ask. */
259
+ export const runOf = (body: JournalEntry[]): JournalRecord[] => {
260
+ const numbers = [
261
+ ...new Set(
262
+ body.flatMap(({ ticket }) => (ticket === null ? [] : [ticket]))
263
+ ),
264
+ ]
265
+ const entries = [
266
+ ...intakePassed({
267
+ tickets: numbers.map((number) => practiceTicket({ number })),
268
+ }),
269
+ ...body,
270
+ ]
271
+ return recordsFrom({
272
+ entries: entries.map((entry, index) =>
273
+ entry.kind === 'jev_answered'
274
+ ? { ...entry, content: { ...entry.content, asked_seq: index } }
275
+ : entry
276
+ ),
277
+ })
278
+ }
279
+
280
+ export const scoreOf = (
281
+ scores: ModelScore[],
282
+ role: Built,
283
+ model: Model
284
+ ): ModelScore | undefined =>
285
+ scores.find((score) => score.role === role && score.model === model)
286
+
287
+ /** The role and model of each score with tickets, sorted. */
288
+ export const groups = (scores: ModelScore[]): string[] =>
289
+ scores
290
+ .filter(({ tickets }) => tickets > 0)
291
+ .map(({ role, model }) => `${role}/${model}`)
292
+ .sort()
293
+
294
+ /** Clef picked `pick` for the test-writer, sure enough to act on it. */
295
+ export const sure = (pick: Model, confidence = 0.9): Ask => ({
296
+ role: 'test-writer',
297
+ pick,
298
+ confidence,
299
+ acted: true,
300
+ })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alecsibilia/luca",
3
3
  "description": "Luca: turns a spec on GitHub Issues into a reviewed pull request, run from Paseo with Claude Code",
4
- "version": "14.0.0-alpha.21",
4
+ "version": "14.0.0-alpha.22",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Alec Sibilia <sibilia.alec@gmail.com>",
7
7
  "homepage": "https://github.com/asibilia/luca-framework#readme",