catherd-cli 0.2.1 → 1.0.0

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.
Files changed (187) hide show
  1. package/README.md +114 -30
  2. package/THIRD_PARTY_NOTICES.md +37 -0
  3. package/catalog/jev.json +89 -0
  4. package/catalog/models.json +236 -0
  5. package/catalog/scores.json +366 -0
  6. package/package.json +24 -8
  7. package/plugin/.claude-plugin/plugin.json +2 -2
  8. package/plugin/.mcp.json +1 -1
  9. package/plugin/commands/catherd.md +1 -1
  10. package/plugin/skills/catherd/SKILL.md +97 -51
  11. package/plugin/skills/catherd-setup/SKILL.md +34 -24
  12. package/src/adapters/all.ts +8 -0
  13. package/src/adapters/backend.ts +144 -0
  14. package/src/adapters/claude-code/events.ts +86 -0
  15. package/src/adapters/claude-code/index.ts +223 -0
  16. package/src/adapters/claude-code/models-api.ts +65 -0
  17. package/src/adapters/claude-code/models.ts +26 -0
  18. package/src/adapters/cli.ts +74 -0
  19. package/src/adapters/codex/events.ts +65 -0
  20. package/src/adapters/codex/home.ts +47 -0
  21. package/src/adapters/codex/index.ts +227 -0
  22. package/src/adapters/discovery.ts +87 -0
  23. package/src/adapters/opencode/agents.ts +84 -0
  24. package/src/adapters/opencode/events.ts +109 -0
  25. package/src/adapters/opencode/index.ts +316 -0
  26. package/src/adapters/registry.ts +14 -0
  27. package/src/cli.ts +159 -19
  28. package/src/domain/agents.ts +60 -0
  29. package/src/domain/budget.ts +45 -0
  30. package/src/domain/catalog.ts +262 -0
  31. package/src/domain/changes.ts +52 -0
  32. package/src/domain/cost.ts +89 -0
  33. package/src/domain/errors.ts +58 -0
  34. package/src/domain/hints.ts +22 -0
  35. package/src/domain/ids.ts +97 -0
  36. package/src/domain/jev.ts +283 -0
  37. package/src/domain/lane.ts +69 -0
  38. package/src/domain/profile-rules.ts +205 -0
  39. package/src/domain/profile.ts +383 -0
  40. package/src/domain/record.ts +84 -0
  41. package/src/{profile → domain}/role-prompts.ts +42 -26
  42. package/src/domain/roles.ts +25 -0
  43. package/src/domain/route.ts +126 -0
  44. package/src/domain/runtime.ts +21 -0
  45. package/src/domain/sanitize.ts +29 -0
  46. package/src/domain/secrets.ts +57 -0
  47. package/src/domain/select.ts +136 -0
  48. package/src/domain/state.ts +33 -0
  49. package/src/domain/util.ts +18 -0
  50. package/src/entry/capture-fixtures-command.ts +57 -0
  51. package/src/entry/catalog-command.ts +106 -0
  52. package/src/entry/cli-kit.ts +36 -0
  53. package/src/entry/deps.ts +16 -0
  54. package/src/entry/doctor-command.ts +45 -0
  55. package/src/entry/glyphs.ts +33 -0
  56. package/src/entry/init-command.ts +142 -0
  57. package/src/entry/lock-command.ts +123 -0
  58. package/src/entry/mcp/dispatch-tools.ts +82 -0
  59. package/src/entry/mcp/handshake.ts +48 -0
  60. package/src/entry/mcp/lane-tools.ts +83 -0
  61. package/src/entry/mcp/result.ts +51 -0
  62. package/src/entry/mcp/run-tools.ts +144 -0
  63. package/src/entry/mcp/server.ts +72 -0
  64. package/src/entry/mcp/setup-tools.ts +81 -0
  65. package/src/entry/profile-command.ts +320 -0
  66. package/src/entry/prompt.ts +103 -0
  67. package/src/entry/runs-command.ts +217 -0
  68. package/src/entry/supervise-bin.ts +18 -0
  69. package/src/entry/supervise-command.ts +37 -0
  70. package/src/entry/tui/commands.ts +545 -0
  71. package/src/entry/tui/effects.ts +270 -0
  72. package/src/entry/tui/fixtures.ts +318 -0
  73. package/src/entry/tui/profile-edits.ts +138 -0
  74. package/src/entry/tui/profile-tree.ts +419 -0
  75. package/src/entry/tui/providers/app.tsx +162 -0
  76. package/src/entry/tui/providers/boundary.tsx +57 -0
  77. package/src/entry/tui/providers/data.tsx +133 -0
  78. package/src/entry/tui/providers/keymap.tsx +166 -0
  79. package/src/entry/tui/providers/root.tsx +45 -0
  80. package/src/entry/tui/providers/theme.tsx +17 -0
  81. package/src/entry/tui/providers/toast.tsx +77 -0
  82. package/src/entry/tui/run.tsx +98 -0
  83. package/src/entry/tui/state.ts +336 -0
  84. package/src/entry/tui/stories.ts +75 -0
  85. package/src/entry/tui/text.ts +118 -0
  86. package/src/entry/tui/theme.ts +139 -0
  87. package/src/entry/tui/views/app.tsx +208 -0
  88. package/src/entry/tui/views/dialogs.tsx +41 -0
  89. package/src/entry/tui/views/profile-actions.ts +321 -0
  90. package/src/entry/tui/views/profiles.tsx +440 -0
  91. package/src/entry/tui/views/runs.tsx +281 -0
  92. package/src/entry/tui/views/save-dialog.tsx +332 -0
  93. package/src/entry/tui/views/status.tsx +206 -0
  94. package/src/entry/tui/widgets/chrome.tsx +114 -0
  95. package/src/entry/tui/widgets/dialog-confirm.tsx +58 -0
  96. package/src/entry/tui/widgets/dialog-prompt.tsx +52 -0
  97. package/src/entry/tui/widgets/dialog-select.tsx +212 -0
  98. package/src/entry/tui/widgets/dialog.tsx +68 -0
  99. package/src/entry/tui/widgets/line.tsx +92 -0
  100. package/src/entry/tui/widgets/list.tsx +196 -0
  101. package/src/entry/tui/widgets/spinner.tsx +49 -0
  102. package/src/entry/tui/widgets/toast.tsx +62 -0
  103. package/src/infra/assets.ts +4 -0
  104. package/src/infra/dispatch-dir.ts +278 -0
  105. package/src/infra/env.ts +43 -0
  106. package/src/infra/filelock.ts +159 -0
  107. package/src/infra/git.ts +91 -0
  108. package/src/infra/heavy-lock.ts +36 -0
  109. package/src/infra/jev-client.ts +162 -0
  110. package/src/infra/launch.ts +25 -0
  111. package/src/infra/log.ts +84 -0
  112. package/src/infra/paths.ts +38 -0
  113. package/src/infra/proc.ts +110 -0
  114. package/src/infra/store.ts +229 -0
  115. package/src/infra/supervisor.ts +257 -0
  116. package/src/infra/version.ts +6 -0
  117. package/src/services/admission.ts +277 -0
  118. package/src/services/agent-links.ts +139 -0
  119. package/src/services/backends.ts +44 -0
  120. package/src/services/budget.ts +47 -0
  121. package/src/services/capture.ts +233 -0
  122. package/src/services/catalog-service.ts +382 -0
  123. package/src/services/dispatch-service.ts +479 -0
  124. package/src/services/dispatches.ts +145 -0
  125. package/src/services/doctor-backends.ts +185 -0
  126. package/src/services/doctor-checks.ts +103 -0
  127. package/src/services/doctor.ts +358 -0
  128. package/src/services/finalize.ts +359 -0
  129. package/src/services/jev-service.ts +183 -0
  130. package/src/services/lane-service.ts +248 -0
  131. package/src/services/ports.ts +132 -0
  132. package/src/services/preflight.ts +207 -0
  133. package/src/services/profile-service.ts +292 -0
  134. package/src/services/profile-store.ts +141 -0
  135. package/src/services/reconcile.ts +101 -0
  136. package/src/services/routing-service.ts +190 -0
  137. package/src/services/run-debug.ts +79 -0
  138. package/src/services/run-service.ts +161 -0
  139. package/src/services/run-store.ts +316 -0
  140. package/src/services/setup.ts +87 -0
  141. package/src/services/state.ts +104 -0
  142. package/src/services/summary.ts +227 -0
  143. package/catalog/catalog.json +0 -112
  144. package/catalog/models-dev.json +0 -56453
  145. package/src/core/codex-events.ts +0 -49
  146. package/src/core/codex.ts +0 -166
  147. package/src/core/harness.ts +0 -89
  148. package/src/core/lanes.ts +0 -67
  149. package/src/core/lock.ts +0 -111
  150. package/src/core/opencode.ts +0 -207
  151. package/src/core/reconcile.ts +0 -26
  152. package/src/core/reply.ts +0 -54
  153. package/src/core/runstore.ts +0 -163
  154. package/src/core/spawn.ts +0 -89
  155. package/src/core/status.ts +0 -193
  156. package/src/files.ts +0 -19
  157. package/src/mcp/dispatch.ts +0 -154
  158. package/src/mcp/lane-tools.ts +0 -200
  159. package/src/mcp/out.ts +0 -6
  160. package/src/mcp/preflight.ts +0 -84
  161. package/src/mcp/run-tools.ts +0 -121
  162. package/src/mcp/runs.ts +0 -118
  163. package/src/mcp/server.ts +0 -26
  164. package/src/mcp/setup-tools.ts +0 -175
  165. package/src/paths.ts +0 -20
  166. package/src/profile/agents.ts +0 -130
  167. package/src/profile/profile.ts +0 -228
  168. package/src/routing/catalog.ts +0 -227
  169. package/src/routing/commands.ts +0 -22
  170. package/src/routing/jev.ts +0 -185
  171. package/src/routing/route.ts +0 -155
  172. package/src/routing/select.ts +0 -90
  173. package/src/tui/backends.ts +0 -57
  174. package/src/tui/commands.ts +0 -53
  175. package/src/tui/dashboard.tsx +0 -245
  176. package/src/tui/editor.tsx +0 -214
  177. package/src/tui/init.tsx +0 -208
  178. package/src/tui/matrix-model.ts +0 -279
  179. package/src/tui/matrix.tsx +0 -512
  180. package/src/tui/profiles.ts +0 -38
  181. package/src/tui/theme.ts +0 -177
  182. package/src/tui/ui.tsx +0 -267
  183. package/src/tui/watch-model.ts +0 -83
  184. package/src/tui/watch.tsx +0 -236
  185. package/src/types.ts +0 -184
  186. package/src/version.ts +0 -7
  187. /package/src/{mcp/commands.ts → entry/mcp/command.ts} +0 -0
@@ -0,0 +1,227 @@
1
+ import { resolve } from "node:path";
2
+ import { type BudgetStatus, budgetStatus } from "../domain/budget.ts";
3
+ import { isCatherdError } from "../domain/errors.ts";
4
+ import type { Tokens } from "../domain/record.ts";
5
+ import { median } from "../domain/util.ts";
6
+ import { nonBlankLines, readJsonl } from "../infra/store.ts";
7
+ import { spendOf } from "./budget.ts";
8
+ import { type DispatchState, liveDispatches } from "./dispatches.ts";
9
+ import type { Deps } from "./ports.ts";
10
+ import {
11
+ findRun,
12
+ listRuns,
13
+ readAgentRuns,
14
+ readRecords,
15
+ readRoutes,
16
+ type Run,
17
+ runPaths,
18
+ } from "./run-store.ts";
19
+
20
+ export interface RunSummary {
21
+ id: string;
22
+ title: string;
23
+ repo: string;
24
+ createdAt: string;
25
+ stateTail: string[];
26
+ live: { name: string; rung: string; state: DispatchState; secs: number; dispatchId: string }[];
27
+ totals: { runs: number; ok: number; notOk: string[]; tokens: Tokens; costUsd: number; wallMinutes: number };
28
+ /** native subagent runs, as the orchestrator reported them: reported, not measured (spec §14) */
29
+ agents: { runs: number; totalTokens: number; costUsd: number };
30
+ jev: { decisions: number; fallbacks: number };
31
+ /** per backend, how many of this run's dispatches ran with the user's harness (native) and isolated */
32
+ harness: { backend: string; native: number; isolated: number }[];
33
+ budget: BudgetStatus | null;
34
+ milestones: string[];
35
+ warnings: string[];
36
+ }
37
+
38
+ /** One run at a glance. Spec §3.4: reads are pure; nothing here finalizes a dispatch or writes a file. */
39
+ export function summarizeRun(deps: Deps, run: Run): RunSummary {
40
+ const now = deps.now();
41
+ const { records, corrupt } = readRecords(run);
42
+ const live = liveDispatches(run, now);
43
+ const agents = readAgentRuns(run);
44
+ const jev = readJsonl<{ source?: string }>(runPaths(run.dir).jev).rows;
45
+ const sum = (f: (t: Tokens) => number) => records.reduce((n, r) => n + f(r.tokens), 0);
46
+ let budget: BudgetStatus | null = null;
47
+ const warnings = corrupt ? [`runs.jsonl: skipped ${corrupt} unreadable row(s)`] : [];
48
+ try {
49
+ budget = budgetStatus(spendOf(run, records, live, now), deps.profiles.forRepo(run.meta.repo).budget);
50
+ } catch (e) {
51
+ warnings.push(`budget: ${isCatherdError(e) ? e.message : String(e)}`);
52
+ }
53
+ return {
54
+ id: run.id,
55
+ title: run.meta.title,
56
+ repo: run.meta.repo,
57
+ createdAt: run.meta.createdAt,
58
+ stateTail: nonBlankLines(runPaths(run.dir).state).slice(-3),
59
+ live: live.map((d) => ({
60
+ name: d.admit.name,
61
+ rung: d.admit.rung,
62
+ state: d.state,
63
+ secs: Math.max(0, Math.round((now - Date.parse(d.admit.admittedAt)) / 1000)),
64
+ dispatchId: d.admit.dispatchId,
65
+ })),
66
+ totals: {
67
+ runs: records.length,
68
+ ok: records.filter((r) => r.status === "ok").length,
69
+ notOk: records.filter((r) => r.status !== "ok").map((r) => `${r.name} (${r.status})`),
70
+ tokens: { input: sum((t) => t.input), cached: sum((t) => t.cached), output: sum((t) => t.output) },
71
+ costUsd: records.reduce((n, r) => n + (r.costUsd ?? 0), 0),
72
+ wallMinutes: Math.round((now - Date.parse(run.meta.createdAt)) / 60_000),
73
+ },
74
+ agents: {
75
+ runs: agents.length,
76
+ totalTokens: agents.reduce((n, a) => n + a.totalTokens, 0),
77
+ costUsd: agents.reduce((n, a) => n + (a.costUsd ?? 0), 0),
78
+ },
79
+ // a jev-kind, lane or default source is a decision Jev did not make whole
80
+ jev: { decisions: jev.length, fallbacks: jev.filter((j) => j.source !== "jev").length },
81
+ harness: [...new Set(records.map((r) => r.backend))].sort().map((backend) => {
82
+ const of = records.filter((r) => r.backend === backend);
83
+ return {
84
+ backend,
85
+ native: of.filter((r) => !r.isolated).length,
86
+ isolated: of.filter((r) => r.isolated).length,
87
+ };
88
+ }),
89
+ budget,
90
+ milestones: nonBlankLines(runPaths(run.dir).ledger).slice(1),
91
+ warnings,
92
+ };
93
+ }
94
+
95
+ /** `status(run?)`: that run; without one, every run with a live role, else the newest run. */
96
+ export function status(
97
+ deps: Deps,
98
+ runId?: string,
99
+ ): { version: string; runs: RunSummary[]; warnings: string[] } {
100
+ if (runId) return { version: deps.version, runs: [summarizeRun(deps, findRun(runId))], warnings: [] };
101
+ const { runs, corrupt } = listRuns();
102
+ const warnings = corrupt.map((c) => `skipped run ${c.id}: ${c.reason}`);
103
+ const all = runs.map((r) => summarizeRun(deps, r));
104
+ const live = all.filter((s) => s.live.length > 0);
105
+ return { version: deps.version, runs: live.length ? live : all.slice(0, 1), warnings };
106
+ }
107
+
108
+ export interface RungStats {
109
+ role: string;
110
+ rung: string;
111
+ runs: number;
112
+ ok: number;
113
+ refusals: number;
114
+ climbsFrom: number;
115
+ secs: number;
116
+ tokens: Tokens;
117
+ }
118
+
119
+ export interface HarnessCost {
120
+ backend: string;
121
+ nativeRuns: number;
122
+ isolatedRuns: number;
123
+ nativeMedian: number | null;
124
+ isolatedMedian: number | null;
125
+ extraPerRun: number | null;
126
+ }
127
+
128
+ /** A median in whole tokens: an even count's mean of the two middle values is rounded. */
129
+ function tokenMedian(xs: number[]): number | null {
130
+ const m = median(xs);
131
+ return m === null ? null : Math.round(m);
132
+ }
133
+
134
+ /** Fewer first-turn samples than this on either side, and the harness line gives no figure. */
135
+ const HARNESS_MIN_RUNS = 3;
136
+
137
+ /**
138
+ * What the user's own harness setup costs per run: median first-turn input, native against isolated.
139
+ * No figure below HARNESS_MIN_RUNS a side, nor when native is not dearer: noise, not a cost.
140
+ */
141
+ function harnessCosts(runs: Run[]): HarnessCost[] {
142
+ type Row = { backend: string; isolated: boolean; firstTurnInput: number };
143
+ const rows = runs.flatMap((r) => readJsonl<Row>(runPaths(r.dir).harness).rows);
144
+ return [...new Set(rows.map((r) => r.backend))].sort().map((backend) => {
145
+ const pick = (isolated: boolean) =>
146
+ rows.filter((r) => r.backend === backend && r.isolated === isolated).map((r) => r.firstTurnInput);
147
+ const nativeRuns = pick(false).length;
148
+ const isolatedRuns = pick(true).length;
149
+ const native = tokenMedian(pick(false));
150
+ const isolated = tokenMedian(pick(true));
151
+ const enough = nativeRuns >= HARNESS_MIN_RUNS && isolatedRuns >= HARNESS_MIN_RUNS;
152
+ const extra = native !== null && isolated !== null ? native - isolated : 0;
153
+ return {
154
+ backend,
155
+ nativeRuns,
156
+ isolatedRuns,
157
+ nativeMedian: native,
158
+ isolatedMedian: isolated,
159
+ extraPerRun: enough && extra > 0 ? extra : null,
160
+ };
161
+ });
162
+ }
163
+
164
+ /** Per role and rung over past runs: runs, ok, refusals, climbs from it, seconds and tokens. Reads only. */
165
+ export function runsSummary(f: { run?: string; repo?: string; role?: string; sinceDays?: number }): {
166
+ rungs: RungStats[];
167
+ agents: { rung: string; runs: number; totalTokens: number; secs: number }[];
168
+ harness: HarnessCost[];
169
+ } {
170
+ const since = f.sinceDays ? Date.now() - f.sinceDays * 86_400_000 : 0;
171
+ const repo = f.repo ? resolve(f.repo) : null;
172
+ const all = listRuns().runs.filter(
173
+ (r) => (!repo || r.meta.repo === repo) && Date.parse(r.meta.createdAt) >= since,
174
+ );
175
+ const runs = all.filter((r) => !f.run || r.id === f.run);
176
+ // with a run given, the harness line compares like with like: every run of that run's repo
177
+ const harnessRepo = f.run ? runs[0]?.meta.repo : undefined;
178
+ const harnessRuns = f.run ? all.filter((r) => r.meta.repo === harnessRepo) : runs;
179
+ const stats = new Map<string, RungStats>();
180
+ const at = (role: string, rung: string): RungStats => {
181
+ const key = `${role} ${rung}`;
182
+ let s = stats.get(key);
183
+ if (!s) {
184
+ s = {
185
+ role,
186
+ rung,
187
+ runs: 0,
188
+ ok: 0,
189
+ refusals: 0,
190
+ climbsFrom: 0,
191
+ secs: 0,
192
+ tokens: { input: 0, cached: 0, output: 0 },
193
+ };
194
+ stats.set(key, s);
195
+ }
196
+ return s;
197
+ };
198
+ const agents = new Map<string, { rung: string; runs: number; totalTokens: number; secs: number }>();
199
+ for (const run of runs) {
200
+ for (const r of readRecords(run).records) {
201
+ if (f.role && r.role !== f.role) continue;
202
+ const s = at(r.role, r.rung);
203
+ s.runs++;
204
+ if (r.status === "ok") s.ok++;
205
+ if (r.replyStatus === "refused" || r.replyStatus === "blocked") s.refusals++;
206
+ s.secs += r.secs;
207
+ s.tokens.input += r.tokens.input;
208
+ s.tokens.cached += r.tokens.cached;
209
+ s.tokens.output += r.tokens.output;
210
+ }
211
+ for (const c of readRoutes(run))
212
+ if (c.source === "climb" && c.from && (!f.role || c.role === f.role)) at(c.role, c.from).climbsFrom++;
213
+ for (const a of readAgentRuns(run)) {
214
+ if (f.role && a.role !== f.role) continue;
215
+ const s = agents.get(a.rung) ?? { rung: a.rung, runs: 0, totalTokens: 0, secs: 0 };
216
+ s.runs++;
217
+ s.totalTokens += a.totalTokens;
218
+ s.secs += a.secs ?? 0;
219
+ agents.set(a.rung, s);
220
+ }
221
+ }
222
+ return {
223
+ rungs: [...stats.values()].sort((a, b) => a.role.localeCompare(b.role) || a.rung.localeCompare(b.rung)),
224
+ agents: [...agents.values()].sort((a, b) => a.rung.localeCompare(b.rung)),
225
+ harness: harnessCosts(harnessRuns),
226
+ };
227
+ }
@@ -1,112 +0,0 @@
1
- {
2
- "version": "2026-09-24",
3
- "sources": {
4
- "repo_code": "DeepSWE-style resolution of real repository issues, per model and effort, from the published results collected in spec §5 on 2026-09-24",
5
- "terminal": "Terminal-Bench-style terminal, ops and environment work, per model and effort, from the same collection",
6
- "honesty": "100 minus the rate of failing to disclose a broken tool, from the system cards: gpt-6-luna 71.3, gpt-6-sol 95.1, claude-opus-5-5 not published",
7
- "secs_per_task": "median seconds per task in the same benchmark runs",
8
- "costRank": "catherd's own order of what one run costs a user on a ChatGPT plan and a Claude plan, lowest first; subscription quota counts"
9
- },
10
- "notes": {
11
- "gpt-6-sol": "context 400000 is unverified for the ChatGPT-login id",
12
- "gpt-6-luna": "context 400000 is unverified for the ChatGPT-login id",
13
- "claude-opus-5-5": "context 1000000 is unverified",
14
- "unscored on purpose": "gpt-6-luna#xhigh was dominated; gpt-6-luna#max took 958 s a task, slower than gpt-6-sol#xhigh at an equal score"
15
- },
16
- "models": [
17
- {
18
- "id": "gpt-6-luna",
19
- "backend": "codex",
20
- "efforts": ["low", "medium", "high", "xhigh", "max"],
21
- "capabilities": {
22
- "toolCall": true,
23
- "imageIn": true,
24
- "imageOut": true,
25
- "reasoning": true,
26
- "context": 400000
27
- }
28
- },
29
- {
30
- "id": "gpt-6-sol",
31
- "backend": "codex",
32
- "efforts": ["low", "medium", "high", "xhigh"],
33
- "capabilities": {
34
- "toolCall": true,
35
- "imageIn": true,
36
- "imageOut": true,
37
- "reasoning": true,
38
- "context": 400000
39
- }
40
- },
41
- {
42
- "id": "claude-opus-5-5",
43
- "backend": "claude",
44
- "efforts": ["low", "medium", "high"],
45
- "capabilities": {
46
- "toolCall": true,
47
- "imageIn": true,
48
- "imageOut": false,
49
- "reasoning": true,
50
- "context": 1000000
51
- }
52
- }
53
- ],
54
- "entries": [
55
- {
56
- "rung": "gpt-6-luna#high",
57
- "scores": { "repo_code": 59.3, "terminal": 4.5, "honesty": 71.3, "secs_per_task": 563 },
58
- "costRank": 1
59
- },
60
- {
61
- "rung": "gpt-6-sol#medium",
62
- "scores": { "repo_code": 56.6, "terminal": 18.7, "honesty": 95.1, "secs_per_task": 264 },
63
- "costRank": 2
64
- },
65
- {
66
- "rung": "gpt-6-sol#high",
67
- "scores": { "repo_code": 65.3, "terminal": 26.3, "honesty": 95.1, "secs_per_task": 391 },
68
- "costRank": 3
69
- },
70
- {
71
- "rung": "gpt-6-sol#xhigh",
72
- "scores": { "repo_code": 66.6, "terminal": 30.3, "honesty": 95.1, "secs_per_task": 496 },
73
- "costRank": 4
74
- },
75
- { "rung": "claude-opus-5-5#low", "scores": { "terminal": 31.3 }, "costRank": 5 },
76
- { "rung": "claude-opus-5-5#medium", "scores": { "terminal": 52.5 }, "costRank": 6 },
77
- { "rung": "claude-opus-5-5#high", "scores": { "terminal": 56.6 }, "costRank": 7 }
78
- ],
79
- "bars": {
80
- "repo_code": {
81
- "copy": { "repo_code": 55 },
82
- "build": { "repo_code": 55 },
83
- "logic": { "repo_code": 55, "terminal": 15 },
84
- "hard": { "repo_code": 56, "terminal": 18 }
85
- },
86
- "terminal": {
87
- "copy": { "terminal": 15 },
88
- "build": { "terminal": 15 },
89
- "logic": { "terminal": 15 },
90
- "hard": { "terminal": 15 }
91
- },
92
- "ui": {
93
- "copy": { "repo_code": 55 },
94
- "build": { "repo_code": 55 },
95
- "logic": { "repo_code": 55, "terminal": 15 },
96
- "hard": { "repo_code": 56, "terminal": 18 }
97
- },
98
- "prose": {
99
- "copy": { "repo_code": 55 },
100
- "build": { "repo_code": 55 },
101
- "logic": { "repo_code": 55, "terminal": 15 },
102
- "hard": { "repo_code": 56, "terminal": 18 }
103
- },
104
- "research": {
105
- "copy": { "repo_code": 55 },
106
- "build": { "repo_code": 55 },
107
- "logic": { "repo_code": 55, "terminal": 15 },
108
- "hard": { "repo_code": 56, "terminal": 18 }
109
- }
110
- },
111
- "treatLike": {}
112
- }