catherd-cli 1.5.0 → 2.0.0-beta.20261022113309

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 (275) hide show
  1. package/README.md +164 -208
  2. package/THIRD_PARTY_NOTICES.md +217 -35
  3. package/catalog/jev.json +27 -1
  4. package/catalog/models.json +56 -247
  5. package/package.json +6 -8
  6. package/plugin/.claude-plugin/plugin.json +1 -1
  7. package/plugin/.codex-plugin/plugin.json +1 -1
  8. package/plugin/bin/catherd-mcp +1 -1
  9. package/plugin/commands/catherd.md +2 -2
  10. package/plugin/skills/catherd/SKILL.md +97 -421
  11. package/plugin/skills/catherd-setup/SKILL.md +20 -19
  12. package/src/adapters/all.ts +0 -4
  13. package/src/adapters/backend.ts +1 -2
  14. package/src/adapters/cursor/home.ts +1 -1
  15. package/src/adapters/cursor/index.ts +4 -4
  16. package/src/cli.ts +22 -27
  17. package/src/domain/bars.ts +33 -107
  18. package/src/domain/blocked.ts +72 -0
  19. package/src/domain/branch.ts +51 -0
  20. package/src/domain/brief.ts +21 -6
  21. package/src/domain/catalog.ts +69 -324
  22. package/src/domain/cost.ts +15 -30
  23. package/src/domain/errors.ts +2 -2
  24. package/src/domain/fail-lines.ts +26 -0
  25. package/src/domain/failover.ts +37 -87
  26. package/src/domain/findings.ts +47 -0
  27. package/src/domain/gate-brief.ts +1 -0
  28. package/src/domain/ids.ts +13 -2
  29. package/src/domain/jev.ts +47 -2
  30. package/src/domain/judge.ts +124 -0
  31. package/src/domain/lane.ts +22 -3
  32. package/src/domain/milestone-memory.ts +53 -0
  33. package/src/domain/notice.ts +25 -8
  34. package/src/domain/notify.ts +32 -0
  35. package/src/domain/plan-doc.ts +198 -0
  36. package/src/domain/plan-lint.ts +141 -0
  37. package/src/domain/profile-rules.ts +101 -160
  38. package/src/domain/profile.ts +116 -63
  39. package/src/domain/record.ts +53 -2
  40. package/src/domain/role-prompts.ts +131 -45
  41. package/src/domain/role-scope.ts +4 -34
  42. package/src/domain/role-tools.ts +2 -2
  43. package/src/domain/roles.ts +2 -0
  44. package/src/domain/route.ts +17 -0
  45. package/src/domain/rules.ts +33 -0
  46. package/src/domain/run-events.ts +298 -0
  47. package/src/domain/run-machine.ts +668 -0
  48. package/src/domain/run-state.ts +1310 -0
  49. package/src/domain/scorecard.ts +197 -0
  50. package/src/domain/select.ts +151 -146
  51. package/src/domain/slopalytics.ts +72 -0
  52. package/src/entry/answer-command.ts +39 -0
  53. package/src/entry/catalog-command.ts +25 -174
  54. package/src/entry/check-bin.ts +17 -0
  55. package/src/entry/cli-kit.ts +1 -1
  56. package/src/entry/daemon-bin.ts +22 -0
  57. package/src/entry/glyphs.ts +15 -2
  58. package/src/entry/init-command.ts +18 -68
  59. package/src/entry/knowledge-command.ts +45 -4
  60. package/src/entry/lock-command.ts +6 -7
  61. package/src/entry/mcp/role-server.ts +18 -0
  62. package/src/entry/mcp/server.ts +13 -40
  63. package/src/entry/mcp/session-tools.ts +149 -0
  64. package/src/entry/panel/openapi.ts +189 -0
  65. package/src/entry/panel/server.ts +410 -0
  66. package/src/entry/panel-command.ts +68 -0
  67. package/src/entry/profile-command.ts +10 -14
  68. package/src/entry/role-cli-command.ts +47 -3
  69. package/src/entry/runs-command.ts +3 -19
  70. package/src/entry/scorecard-command.ts +187 -0
  71. package/src/entry/supervise-bin.ts +1 -1
  72. package/src/infra/env.ts +0 -2
  73. package/src/infra/git.ts +64 -0
  74. package/src/infra/heavy-lock.ts +45 -7
  75. package/src/infra/launch.ts +63 -7
  76. package/src/infra/memory.ts +45 -0
  77. package/src/infra/ntfy.ts +40 -0
  78. package/src/infra/open-url.ts +42 -0
  79. package/src/infra/paths.ts +2 -2
  80. package/src/infra/slopalytics.ts +30 -0
  81. package/src/infra/store.ts +6 -1
  82. package/src/services/admission.ts +24 -9
  83. package/src/services/backends.ts +4 -6
  84. package/src/services/capture.ts +1 -22
  85. package/src/services/catalog-service.ts +155 -262
  86. package/src/services/credentials.ts +4 -20
  87. package/src/services/daemon.ts +181 -0
  88. package/src/services/dispatch-service.ts +5 -1
  89. package/src/services/doctor-backends.ts +1 -1
  90. package/src/services/doctor-checks.ts +47 -0
  91. package/src/services/doctor.ts +4 -9
  92. package/src/services/failover-rungs.ts +109 -0
  93. package/src/services/finalize.ts +12 -0
  94. package/src/services/gate-env.ts +1 -2
  95. package/src/services/gate-service.ts +21 -6
  96. package/src/services/jev-service.ts +2 -4
  97. package/src/services/lane-service.ts +34 -3
  98. package/src/services/milestone-check.ts +433 -0
  99. package/src/services/milestone-tree.ts +170 -0
  100. package/src/services/milestones.ts +35 -8
  101. package/src/services/notifier.ts +69 -2
  102. package/src/services/notify-service.ts +26 -0
  103. package/src/services/panel-setup.ts +116 -0
  104. package/src/services/plan-input.ts +151 -0
  105. package/src/services/ports.ts +30 -4
  106. package/src/services/preflight.ts +82 -8
  107. package/src/services/profile-service.ts +30 -3
  108. package/src/services/profile-store.ts +8 -3
  109. package/src/services/protocol.ts +4 -0
  110. package/src/services/provenance.ts +31 -90
  111. package/src/services/questions.ts +9 -0
  112. package/src/services/reconcile.ts +11 -0
  113. package/src/services/routing-service.ts +26 -15
  114. package/src/services/run-briefs.ts +477 -0
  115. package/src/services/run-client.ts +217 -0
  116. package/src/services/run-effects.ts +837 -0
  117. package/src/services/run-events-store.ts +174 -0
  118. package/src/services/run-evidence.ts +1 -1
  119. package/src/services/run-read.ts +221 -0
  120. package/src/services/run-report.ts +176 -0
  121. package/src/services/run-service.ts +55 -6
  122. package/src/services/run-store.ts +54 -7
  123. package/src/services/scorecard-metrics.ts +86 -0
  124. package/src/services/scorecard.ts +382 -0
  125. package/src/services/summary.ts +27 -3
  126. package/src/services/tree-path.ts +27 -0
  127. package/src/services/workspace-admission.ts +5 -13
  128. package/src/services/workspace-service.ts +5 -0
  129. package/web/dist/assets/Brand-PKlUX5TE.js +1 -0
  130. package/web/dist/assets/Brand-PKlUX5TE.js.br +0 -0
  131. package/web/dist/assets/Brand-PKlUX5TE.js.gz +0 -0
  132. package/web/dist/assets/Modal-Dxc5so53.js +1 -0
  133. package/web/dist/assets/Modal-Dxc5so53.js.br +0 -0
  134. package/web/dist/assets/Modal-Dxc5so53.js.gz +0 -0
  135. package/web/dist/assets/Pressable-C8-T6Ed_.js +1 -0
  136. package/web/dist/assets/StatusChip-B5xfI_Mn.js +153 -0
  137. package/web/dist/assets/StatusChip-B5xfI_Mn.js.br +0 -0
  138. package/web/dist/assets/StatusChip-B5xfI_Mn.js.gz +0 -0
  139. package/web/dist/assets/_app-Ea5cpyKe.js +1 -0
  140. package/web/dist/assets/_app-Ea5cpyKe.js.br +0 -0
  141. package/web/dist/assets/_app-Ea5cpyKe.js.gz +0 -0
  142. package/web/dist/assets/_runId-pTG0fXVo.js +1 -0
  143. package/web/dist/assets/_runId-pTG0fXVo.js.br +0 -0
  144. package/web/dist/assets/_runId-pTG0fXVo.js.gz +0 -0
  145. package/web/dist/assets/drawer-Du3cdlok.js +1 -0
  146. package/web/dist/assets/drawer-Du3cdlok.js.br +0 -0
  147. package/web/dist/assets/drawer-Du3cdlok.js.gz +0 -0
  148. package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  149. package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  150. package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  151. package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  152. package/web/dist/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  153. package/web/dist/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  154. package/web/dist/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  155. package/web/dist/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  156. package/web/dist/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  157. package/web/dist/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  158. package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  159. package/web/dist/assets/index-B6GiwDkh.js +11 -0
  160. package/web/dist/assets/index-B6GiwDkh.js.br +0 -0
  161. package/web/dist/assets/index-B6GiwDkh.js.gz +0 -0
  162. package/web/dist/assets/index-C5IbylvM.css +2 -0
  163. package/web/dist/assets/index-C5IbylvM.css.br +0 -0
  164. package/web/dist/assets/index-C5IbylvM.css.gz +0 -0
  165. package/web/dist/assets/label-DFSYBc62.js +1 -0
  166. package/web/dist/assets/label-DFSYBc62.js.br +0 -0
  167. package/web/dist/assets/label-DFSYBc62.js.gz +0 -0
  168. package/web/dist/assets/link-D8EcKZUt.js +1 -0
  169. package/web/dist/assets/link-D8EcKZUt.js.br +0 -0
  170. package/web/dist/assets/link-D8EcKZUt.js.gz +0 -0
  171. package/web/dist/assets/login-DaN5HPPW.js +1 -0
  172. package/web/dist/assets/login-DaN5HPPW.js.br +0 -0
  173. package/web/dist/assets/login-DaN5HPPW.js.gz +0 -0
  174. package/web/dist/assets/query-Cu--ccT3.js +1 -0
  175. package/web/dist/assets/questions-shoO9OVj.js +1 -0
  176. package/web/dist/assets/questions-shoO9OVj.js.br +0 -0
  177. package/web/dist/assets/questions-shoO9OVj.js.gz +0 -0
  178. package/web/dist/assets/react-query.gen-Bu2yV-qJ.js +12 -0
  179. package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.br +0 -0
  180. package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.gz +0 -0
  181. package/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  182. package/web/dist/assets/run-status-C0DRuV7U.js +1 -0
  183. package/web/dist/assets/runs-C3rg2zZc.js +1 -0
  184. package/web/dist/assets/runs-C3rg2zZc.js.br +0 -0
  185. package/web/dist/assets/runs-C3rg2zZc.js.gz +0 -0
  186. package/web/dist/assets/setup-5I4Vl9fR.js +1 -0
  187. package/web/dist/assets/setup-5I4Vl9fR.js.br +0 -0
  188. package/web/dist/assets/setup-5I4Vl9fR.js.gz +0 -0
  189. package/web/dist/assets/table-BOxXhqS4.js +1 -0
  190. package/web/dist/assets/table-BOxXhqS4.js.br +0 -0
  191. package/web/dist/assets/table-BOxXhqS4.js.gz +0 -0
  192. package/web/dist/assets/textfield-BzRFkxBF.js +1 -0
  193. package/web/dist/assets/textfield-BzRFkxBF.js.br +0 -0
  194. package/web/dist/assets/textfield-BzRFkxBF.js.gz +0 -0
  195. package/web/dist/assets/time--9kSQbbp.js +1 -0
  196. package/web/dist/assets/use-css-variable-CpnN9WtA.js +5 -0
  197. package/web/dist/assets/use-css-variable-CpnN9WtA.js.br +0 -0
  198. package/web/dist/assets/use-css-variable-CpnN9WtA.js.gz +0 -0
  199. package/web/dist/assets/useMutation-CVPKlR34.js +1 -0
  200. package/web/dist/assets/useMutation-CVPKlR34.js.br +0 -0
  201. package/web/dist/assets/useMutation-CVPKlR34.js.gz +0 -0
  202. package/web/dist/index.html +15 -0
  203. package/catalog/ATTRIBUTION.md +0 -44
  204. package/catalog/scores.json +0 -3202
  205. package/catalog/sources.json +0 -97
  206. package/src/adapters/antigravity/events.ts +0 -131
  207. package/src/adapters/antigravity/home.ts +0 -46
  208. package/src/adapters/antigravity/index.ts +0 -335
  209. package/src/adapters/antigravity/models.ts +0 -37
  210. package/src/adapters/grok/events.ts +0 -112
  211. package/src/adapters/grok/home.ts +0 -187
  212. package/src/adapters/grok/index.ts +0 -358
  213. package/src/adapters/grok/models.ts +0 -48
  214. package/src/domain/calibration.ts +0 -132
  215. package/src/domain/sources.ts +0 -144
  216. package/src/entry/mcp/dispatch-tools.ts +0 -52
  217. package/src/entry/mcp/lane-tools.ts +0 -131
  218. package/src/entry/mcp/protocol-tools.ts +0 -63
  219. package/src/entry/mcp/push-tools.ts +0 -16
  220. package/src/entry/mcp/run-tools.ts +0 -164
  221. package/src/entry/mcp/setup-tools.ts +0 -109
  222. package/src/entry/mcp/workspace-tools.ts +0 -142
  223. package/src/entry/tui/commands.ts +0 -563
  224. package/src/entry/tui/effects.ts +0 -385
  225. package/src/entry/tui/fixtures.ts +0 -637
  226. package/src/entry/tui/profile-edits.ts +0 -146
  227. package/src/entry/tui/profile-tree.ts +0 -429
  228. package/src/entry/tui/providers/app.tsx +0 -162
  229. package/src/entry/tui/providers/boundary.tsx +0 -57
  230. package/src/entry/tui/providers/data.tsx +0 -160
  231. package/src/entry/tui/providers/keymap.tsx +0 -166
  232. package/src/entry/tui/providers/root.tsx +0 -45
  233. package/src/entry/tui/providers/theme.tsx +0 -17
  234. package/src/entry/tui/providers/toast.tsx +0 -77
  235. package/src/entry/tui/run.tsx +0 -114
  236. package/src/entry/tui/state.ts +0 -400
  237. package/src/entry/tui/stories.ts +0 -114
  238. package/src/entry/tui/text.ts +0 -130
  239. package/src/entry/tui/theme.ts +0 -140
  240. package/src/entry/tui/views/app.tsx +0 -208
  241. package/src/entry/tui/views/dialogs.tsx +0 -41
  242. package/src/entry/tui/views/profile-actions.ts +0 -331
  243. package/src/entry/tui/views/profiles.tsx +0 -520
  244. package/src/entry/tui/views/runs.tsx +0 -619
  245. package/src/entry/tui/views/save-dialog.tsx +0 -344
  246. package/src/entry/tui/views/status.tsx +0 -213
  247. package/src/entry/tui/widgets/chrome.tsx +0 -114
  248. package/src/entry/tui/widgets/dialog-confirm.tsx +0 -58
  249. package/src/entry/tui/widgets/dialog-prompt.tsx +0 -52
  250. package/src/entry/tui/widgets/dialog-select.tsx +0 -212
  251. package/src/entry/tui/widgets/dialog.tsx +0 -68
  252. package/src/entry/tui/widgets/line.tsx +0 -92
  253. package/src/entry/tui/widgets/list.tsx +0 -197
  254. package/src/entry/tui/widgets/spinner.tsx +0 -49
  255. package/src/entry/tui/widgets/toast.tsx +0 -62
  256. package/src/infra/sources/arena.ts +0 -95
  257. package/src/infra/sources/artificial-analysis.ts +0 -119
  258. package/src/infra/sources/cache.ts +0 -126
  259. package/src/infra/sources/csv.ts +0 -38
  260. package/src/infra/sources/epoch.ts +0 -82
  261. package/src/infra/sources/http.ts +0 -75
  262. package/src/infra/sources/litellm.ts +0 -58
  263. package/src/infra/sources/models-dev.ts +0 -59
  264. package/src/infra/sources/openrouter-endpoints.ts +0 -52
  265. package/src/infra/sources/openrouter-models.ts +0 -60
  266. package/src/infra/sources/rows.ts +0 -38
  267. package/src/infra/sources/vectara.ts +0 -69
  268. package/src/infra/sources/zip.ts +0 -48
  269. package/src/services/catalog-refresh.ts +0 -201
  270. package/src/services/doctor-sources.ts +0 -76
  271. package/src/services/runs-page.ts +0 -290
  272. package/src/services/source-derive.ts +0 -397
  273. package/src/services/source-sync.ts +0 -312
  274. package/src/services/standins.ts +0 -238
  275. package/src/services/treat-likes.ts +0 -113
@@ -0,0 +1,382 @@
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readdirSync,
6
+ readFileSync,
7
+ realpathSync,
8
+ rmSync,
9
+ writeFileSync,
10
+ } from "node:fs";
11
+ import { dirname, join, resolve } from "node:path";
12
+ import { z } from "zod";
13
+ import { CatherdError } from "../domain/errors.ts";
14
+ import { latestOutcomes } from "../domain/route.ts";
15
+ import {
16
+ assertLabel,
17
+ SCORECARD_TASKS,
18
+ assertScorecardRows,
19
+ type ScorecardFile,
20
+ ScorecardFileSchema,
21
+ type ScorecardTask,
22
+ ScorecardTaskSchema,
23
+ type ScorecardTaskId,
24
+ type TaskScore,
25
+ } from "../domain/scorecard.ts";
26
+ import { assetPath } from "../infra/assets.ts";
27
+ import { git, gitHead } from "../infra/git.ts";
28
+ import { dataDir } from "../infra/paths.ts";
29
+ import { ensurePrivateDir, readVersioned, writeJsonAtomic } from "../infra/store.ts";
30
+ import { landedMilestones, reviewRounds } from "./milestones.ts";
31
+ import { checkEnvFor, runCheck } from "./preflight.ts";
32
+ import { runMilestones } from "./protocol.ts";
33
+ import { openQuestions } from "./questions.ts";
34
+ import {
35
+ findRun,
36
+ listRuns,
37
+ readAgentRuns,
38
+ readOutcomes,
39
+ readRecords,
40
+ type Run,
41
+ supersededBy,
42
+ } from "./run-store.ts";
43
+ import { failoverRecovered, interventionsOf, pausedMs, runEnd } from "./scorecard-metrics.ts";
44
+ import { quotaByBackend } from "./summary.ts";
45
+
46
+ // Plan 28: prepare a scorecard task's reproducible starting point, score the run that ended there, store the row.
47
+
48
+ /** The checkout's `scorecard/` folder, or null for an installed package (it ships no fixtures). */
49
+ export const scorecardRoot = (): string | null => {
50
+ const root = assetPath("scorecard");
51
+ return existsSync(root) ? root : null;
52
+ };
53
+
54
+ export const defaultPreparedRoot = (): string => join(dataDir(), "scorecard");
55
+
56
+ const minutes = (ms: number): number => Math.round(ms / 6000) / 10;
57
+
58
+ export function loadTask(id: string, root: string): ScorecardTask {
59
+ if (!(SCORECARD_TASKS as readonly string[]).includes(id))
60
+ throw new CatherdError("E_INPUT_INVALID", `no scorecard task ${id}`, {
61
+ fix: `catherd scorecard prepare ${SCORECARD_TASKS.join("|")}`,
62
+ });
63
+ const file = join(root, "tasks", id, "task.json");
64
+ const task = readVersioned(file, ScorecardTaskSchema, 1);
65
+ if (task.id !== id)
66
+ throw new CatherdError("E_CONFIG_INVALID", `${file} names task ${task.id}, not ${id}`, {
67
+ fix: `set "id" to ${id} in ${file}`,
68
+ });
69
+ return task;
70
+ }
71
+
72
+ const PreparedSchema = z.looseObject({
73
+ schema: z.literal(1),
74
+ task: z.enum(SCORECARD_TASKS),
75
+ dir: z.string(),
76
+ repo: z.string(),
77
+ plan: z.string(),
78
+ startCommit: z.string(),
79
+ limits: z.string().nullable(),
80
+ shim: z.string().nullable(),
81
+ preparedAt: z.string(),
82
+ prompt: z.string(),
83
+ });
84
+ export type Prepared = z.infer<typeof PreparedSchema> & { task: ScorecardTaskId };
85
+
86
+ /** A URL (`scheme://`) or an scp-like `user@host:path`: anything else is a local path. */
87
+ const REMOTE = /^(?:[a-z][a-z0-9+.-]*:\/\/|[^/\s]+@[^/\s:]+:)/i;
88
+
89
+ const GIT_LONG = 30 * 60_000;
90
+
91
+ /** A git command that must succeed: its failure is `fail`'s error. */
92
+ async function must(
93
+ repo: string,
94
+ args: string[],
95
+ fail: () => CatherdError,
96
+ timeoutMs?: number,
97
+ ): Promise<string> {
98
+ const r = await git(repo, args, timeoutMs);
99
+ if (r.kind !== "ok") throw fail();
100
+ return r.out;
101
+ }
102
+
103
+ export async function prepareTask(i: {
104
+ task: ScorecardTask;
105
+ root: string;
106
+ into: string;
107
+ source?: string;
108
+ now: Date;
109
+ setup: boolean;
110
+ }): Promise<Prepared> {
111
+ const { task } = i;
112
+ const given = i.source ?? task.source;
113
+ if (!given)
114
+ throw new CatherdError("E_INPUT_INVALID", `${task.id} names no source`, {
115
+ fix: `catherd scorecard prepare ${task.id} --source <path or URL>`,
116
+ });
117
+ // the clone runs inside the new folder, so a local path must be absolute before it gets there
118
+ const src = REMOTE.test(given) ? given : resolve(given);
119
+ ensurePrivateDir(dirname(i.into));
120
+ try {
121
+ mkdirSync(i.into);
122
+ } catch (e) {
123
+ if ((e as NodeJS.ErrnoException).code === "EEXIST")
124
+ throw new CatherdError("E_INPUT_INVALID", `${i.into} already exists`, {
125
+ fix: "prepare again: the folder name carries the time",
126
+ });
127
+ throw e;
128
+ }
129
+ // the run's own repo toplevel is the real path; findRun and listRuns key runs by it
130
+ const into = realpathSync(i.into);
131
+ const repo = join(into, "repo");
132
+ const io = (what: string) => () =>
133
+ new CatherdError("E_IO_UNEXPECTED", `${what} failed in ${into}`, {
134
+ fix: `check that git works in ${into}`,
135
+ });
136
+ await must(
137
+ into,
138
+ ["clone", "--quiet", "--no-checkout", src, "repo"],
139
+ () =>
140
+ new CatherdError("E_INPUT_INVALID", `cannot clone ${src}`, {
141
+ fix: `catherd scorecard prepare ${task.id} --source <path or URL>`,
142
+ }),
143
+ GIT_LONG,
144
+ );
145
+ await must(
146
+ repo,
147
+ ["checkout", "--quiet", "-b", `scorecard/${task.id}`, task.commit],
148
+ () =>
149
+ new CatherdError("E_INPUT_INVALID", `${src} has no commit ${task.commit}`, {
150
+ fix: `catherd scorecard prepare ${task.id} --source <a clone that has it>`,
151
+ }),
152
+ );
153
+ await must(repo, ["remote", "remove", "origin"], io("git remote remove"));
154
+
155
+ if (task.inject) {
156
+ for (const e of task.inject.edits) {
157
+ const file = join(repo, e.path);
158
+ const text = existsSync(file) ? readFileSync(file, "utf8") : "";
159
+ const parts = text.split(e.find);
160
+ if (parts.length !== 2)
161
+ throw new CatherdError(
162
+ "E_CONFIG_INVALID",
163
+ `${e.path}: the inject text is there ${parts.length - 1} times, not once`,
164
+ {
165
+ fix: `update scorecard/tasks/${task.id}/task.json`,
166
+ },
167
+ );
168
+ writeFileSync(file, parts.join(e.replace));
169
+ }
170
+ await must(repo, ["add", "-A"], io("git add"));
171
+ await must(
172
+ repo,
173
+ [
174
+ "-c",
175
+ "user.name=catherd-scorecard",
176
+ "-c",
177
+ "user.email=scorecard@catherd.invalid",
178
+ "commit",
179
+ "--quiet",
180
+ "-m",
181
+ task.inject.message,
182
+ ],
183
+ io("git commit"),
184
+ );
185
+ }
186
+
187
+ let plan: string;
188
+ if ("file" in task.plan) {
189
+ plan = join(into, "plan.md");
190
+ copyFileSync(join(i.root, "tasks", task.id, task.plan.file), plan);
191
+ } else {
192
+ plan = join(repo, task.plan.repoPath);
193
+ if (!existsSync(plan))
194
+ throw new CatherdError("E_CONFIG_INVALID", `${task.plan.repoPath} is not in ${src} at ${task.commit}`, {
195
+ fix: `update scorecard/tasks/${task.id}/task.json`,
196
+ });
197
+ }
198
+
199
+ let limits: string | null = null;
200
+ if (task.limits > 0) {
201
+ limits = join(into, "limits");
202
+ mkdirSync(join(limits, "taken"), { recursive: true });
203
+ mkdirSync(join(limits, "tokens"));
204
+ for (let n = 1; n <= task.limits; n++)
205
+ writeFileSync(join(limits, "tokens", String(n)), "", { flag: "w" });
206
+ }
207
+
208
+ if (i.setup) {
209
+ const env = await checkEnvFor(repo);
210
+ for (const cmd of task.setup) {
211
+ const r = await runCheck(repo, cmd, GIT_LONG, env);
212
+ if (r.code !== 0 || r.timedOut)
213
+ throw new CatherdError(
214
+ "E_IO_UNEXPECTED",
215
+ `setup \`${cmd}\` failed in ${repo}: ${r.tail.at(-1) ?? "no output"}`,
216
+ {
217
+ fix: `fix it and run it in ${repo}, or prepare again`,
218
+ },
219
+ );
220
+ }
221
+ }
222
+
223
+ const startCommit = (await must(repo, ["rev-parse", "HEAD"], io("git rev-parse"))).trim();
224
+ const prepared: Prepared = {
225
+ schema: 1,
226
+ task: task.id,
227
+ dir: into,
228
+ repo,
229
+ plan,
230
+ startCommit,
231
+ limits,
232
+ shim: limits ? join(i.root, "shim") : null,
233
+ preparedAt: i.now.toISOString(),
234
+ prompt: task.prompt.replaceAll("{plan}", () => plan).replaceAll("{acceptance}", () => task.acceptance),
235
+ };
236
+ writeJsonAtomic(join(into, "prepared.json"), prepared);
237
+ return prepared;
238
+ }
239
+
240
+ export function readPrepared(dir: string): Prepared {
241
+ const file = join(dir, "prepared.json");
242
+ if (!existsSync(file))
243
+ throw new CatherdError("E_INPUT_INVALID", `${dir} is not a prepared scorecard task`, {
244
+ fix: `catherd scorecard prepare ${SCORECARD_TASKS.join("|")}`,
245
+ });
246
+ return readVersioned(file, PreparedSchema, 1) as Prepared;
247
+ }
248
+
249
+ /** The one run to score: the named one, else the only open run created in the prepared repo since it was prepared. */
250
+ function pickRun(p: Prepared, id: string | undefined): Run {
251
+ if (id) {
252
+ const run = findRun(id);
253
+ if (run.meta.repo !== p.repo)
254
+ throw new CatherdError("E_INPUT_INVALID", `run ${id} belongs to ${run.meta.repo}, not ${p.repo}`, {
255
+ fix: "name a run of the prepared repo, or leave --run out",
256
+ });
257
+ return run;
258
+ }
259
+ const since = Date.parse(p.preparedAt);
260
+ const runs = listRuns([p.repo]).runs.filter(
261
+ (r) => Date.parse(r.meta.createdAt) >= since && !supersededBy(r),
262
+ );
263
+ if (runs.length === 0)
264
+ throw new CatherdError("E_INPUT_INVALID", `no run in ${p.repo} yet`, {
265
+ fix: "run the task's start line, paste its prompt, and record once it has landed or been given up",
266
+ });
267
+ if (runs.length > 1)
268
+ throw new CatherdError("E_INPUT_INVALID", `${p.repo} has runs ${runs.map((r) => r.id).join(", ")}`, {
269
+ fix: `catherd scorecard record ${p.dir} --label <l> --run <id>`,
270
+ });
271
+ return runs[0] as Run;
272
+ }
273
+
274
+ export async function scoreTask(i: {
275
+ dir: string;
276
+ root: string;
277
+ run?: string;
278
+ chat?: number;
279
+ now: number;
280
+ }): Promise<TaskScore> {
281
+ const p = readPrepared(i.dir);
282
+ const task = loadTask(p.task, i.root);
283
+ const run = pickRun(p, i.run);
284
+ const milestones = runMilestones(run);
285
+ const landed = landedMilestones(run);
286
+ const finished =
287
+ milestones.length > 0 &&
288
+ milestones.every((m) => landed.includes(m)) &&
289
+ openQuestions(run).length === 0 &&
290
+ !supersededBy(run);
291
+ const end = runEnd(run);
292
+ const start = Date.parse(run.meta.createdAt);
293
+
294
+ const copies = task.acceptFiles.map((f) => ({
295
+ from: join(i.root, "tasks", task.id, f.from),
296
+ to: join(p.repo, f.to),
297
+ }));
298
+ for (const c of copies)
299
+ if (existsSync(c.to))
300
+ throw new CatherdError(
301
+ "E_INPUT_INVALID",
302
+ `${c.to.slice(p.repo.length + 1)} already exists in the run's tree`,
303
+ {
304
+ fix: "the run wrote a file the hidden acceptance test needs; rename the file in the run, or score it by hand",
305
+ },
306
+ );
307
+ const done: string[] = [];
308
+ let check: Awaited<ReturnType<typeof runCheck>>;
309
+ const head = await gitHead(p.repo);
310
+ try {
311
+ for (const c of copies) {
312
+ mkdirSync(dirname(c.to), { recursive: true });
313
+ copyFileSync(c.from, c.to);
314
+ done.push(c.to);
315
+ }
316
+ check = await runCheck(
317
+ p.repo,
318
+ task.acceptance,
319
+ task.acceptanceTimeoutMin * 60_000,
320
+ await checkEnvFor(p.repo),
321
+ );
322
+ } finally {
323
+ for (const f of done) rmSync(f, { force: true });
324
+ }
325
+ const acceptance = {
326
+ passed: check.code === 0 && !check.timedOut,
327
+ code: check.code,
328
+ timedOut: check.timedOut,
329
+ tail: check.tail,
330
+ };
331
+
332
+ const outcomes = latestOutcomes(readOutcomes(run));
333
+ const takenDir = p.limits ? join(p.limits, "taken") : null;
334
+ return {
335
+ task: task.id,
336
+ run: run.id,
337
+ catherdVersion: run.meta.catherdVersion,
338
+ startCommit: p.startCommit,
339
+ head,
340
+ milestones,
341
+ landed,
342
+ finished,
343
+ interventions: interventionsOf(run, end, i.chat ?? 0),
344
+ reviewRounds: milestones.reduce((n, m) => n + reviewRounds(run, m), 0),
345
+ lanes: {
346
+ total: outcomes.length,
347
+ landed: outcomes.filter((o) => o.landed).length,
348
+ climbs: outcomes.reduce((n, o) => n + o.climbs.length, 0),
349
+ },
350
+ wallMinutes: minutes(end - start),
351
+ pausedMinutes: minutes(pausedMs(run, end)),
352
+ quota: {
353
+ backends: quotaByBackend(readRecords(run).records),
354
+ claudeAgentTokens: readAgentRuns(run).reduce((n, a) => n + a.totalTokens, 0),
355
+ },
356
+ acceptance,
357
+ firstTry: acceptance.passed && finished && new Set(landed).size === landed.length,
358
+ injected:
359
+ task.limits > 0
360
+ ? {
361
+ limits: takenDir && existsSync(takenDir) ? readdirSync(takenDir).length : 0,
362
+ standInOk: failoverRecovered(run),
363
+ }
364
+ : null,
365
+ recordedAt: new Date(i.now).toISOString(),
366
+ };
367
+ }
368
+
369
+ export const resultsFile = (dir: string, label: string): string => join(dir, `${assertLabel(label)}.json`);
370
+
371
+ export function readScorecard(file: string): ScorecardFile | null {
372
+ return existsSync(file) ? assertScorecardRows(readVersioned(file, ScorecardFileSchema, 1), file) : null;
373
+ }
374
+
375
+ /** Sets the task's row in the label's file (a re-record replaces it) and returns the file's contents. */
376
+ export function saveTaskScore(file: string, label: string, s: TaskScore): ScorecardFile {
377
+ const f = readScorecard(file) ?? { schema: 1 as const, label, tasks: {} };
378
+ f.tasks[s.task] = s;
379
+ ensurePrivateDir(dirname(file));
380
+ writeJsonAtomic(file, f);
381
+ return f;
382
+ }
@@ -3,11 +3,13 @@ import type { QueueCapability } from "../infra/codex-queue.ts";
3
3
  import { inspectionHost, inspectDeliveries, type DeliveryInspection } from "./run-debug.ts";
4
4
  import { resolve } from "node:path";
5
5
  import { type BudgetStatus, budgetStatus } from "../domain/budget.ts";
6
- import { isCatherdError } from "../domain/errors.ts";
7
- import type { Tokens } from "../domain/record.ts";
6
+ import { errorMessage, isCatherdError } from "../domain/errors.ts";
7
+ import type { RunRecord, Tokens } from "../domain/record.ts";
8
+ import type { QuotaRow } from "../domain/scorecard.ts";
8
9
  import { median } from "../domain/util.ts";
9
10
  import { nonBlankLines, readJsonl } from "../infra/store.ts";
10
11
  import { spendOf } from "./budget.ts";
12
+ import { ensureDaemon } from "./daemon.ts";
11
13
  import { verifierStepView, type VerifierStepView } from "./verifier-step.ts";
12
14
  import { type Pause, pausesOver } from "./pause.ts";
13
15
  import { pinChanges } from "./run-pin.ts";
@@ -64,6 +66,13 @@ export interface RunSummary {
64
66
  /** One run at a glance. Spec §3.4: reads are pure; nothing here finalizes a dispatch or writes a file. */
65
67
  export function summarizeRun(deps: Deps, run: Run): RunSummary {
66
68
  const now = deps.now();
69
+ const warnings: string[] = [];
70
+ // an unfinished 2.0 run whose daemon died gets it back from any reader: status, watch, the panel
71
+ try {
72
+ ensureDaemon(run);
73
+ } catch (e) {
74
+ warnings.push(`daemon: ${errorMessage(e)}`);
75
+ }
67
76
  const { records, corrupt } = readRecords(run);
68
77
  const live = liveDispatches(run, now);
69
78
  const recorded = new Set(records.map((r) => r.dispatchId));
@@ -71,7 +80,7 @@ export function summarizeRun(deps: Deps, run: Run): RunSummary {
71
80
  const jev = readJsonl<{ source?: string }>(runPaths(run.dir).jev).rows;
72
81
  const sum = (f: (t: Tokens) => number) => records.reduce((n, r) => n + f(r.tokens), 0);
73
82
  let budget: BudgetStatus | null = null;
74
- const warnings = corrupt ? [`runs.jsonl: skipped ${corrupt} unreadable row(s)`] : [];
83
+ if (corrupt) warnings.push(`runs.jsonl: skipped ${corrupt} unreadable row(s)`);
75
84
  // spec 1.5 "Pinned per run": a change since the pin, which dispatch does not follow
76
85
  const pinned = pinChanges(deps, run);
77
86
  for (const c of pinned) warnings.push(`pinned: ${c}; dispatch keeps the pinned value (run_pin re-pins)`);
@@ -250,6 +259,21 @@ function harnessCosts(runs: Run[]): HarnessCost[] {
250
259
  });
251
260
  }
252
261
 
262
+ /** Plan 28: tokens and usage limits per backend over `records`, sorted by backend. */
263
+ export function quotaByBackend(records: RunRecord[]): QuotaRow[] {
264
+ const by = new Map<string, QuotaRow>();
265
+ for (const r of records) {
266
+ const q = by.get(r.backend) ?? { backend: r.backend, runs: 0, input: 0, cached: 0, output: 0, limits: 0 };
267
+ q.runs++;
268
+ q.input += r.tokens.input;
269
+ q.cached += r.tokens.cached;
270
+ q.output += r.tokens.output;
271
+ if (r.status === "limit") q.limits++;
272
+ by.set(r.backend, q);
273
+ }
274
+ return [...by.values()].sort((a, b) => a.backend.localeCompare(b.backend));
275
+ }
276
+
253
277
  /** Per role and rung over past runs: runs, ok, refusals, climbs from it, seconds and tokens. Reads only. */
254
278
  export function runsSummary(f: { run?: string; repo?: string; role?: string; sinceDays?: number }): {
255
279
  rungs: RungStats[];
@@ -0,0 +1,27 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { CatherdError } from "../domain/errors.ts";
4
+ import type { Run } from "./run-store.ts";
5
+
6
+ /** `<run dir>/trees/<m>`: where milestone `m` of a base run works. */
7
+ export const treeDir = (run: Run, m: string): string => join(run.dir, "trees", m);
8
+
9
+ /** The marker beside the tree, never inside it, that Setup: finished. */
10
+ export const readyFile = (run: Run, m: string): string => join(run.dir, "trees", `${m}.ready`);
11
+
12
+ /**
13
+ * Where milestone `m` works: its worktree in a base run once it exists, else the repo (a run without base, the
14
+ * run itself, a milestone not claimed yet). A claimed milestone whose folder is gone is an error, never the repo:
15
+ * its commits, checks and merge would land in the owner's own checkout. Kept apart from milestone-tree.ts so the services that only ask
16
+ * where a milestone works do not import the claiming code, which imports them.
17
+ */
18
+ export function treeOf(run: Run, m: string | null): string {
19
+ if (!run.meta.base || m === null) return run.meta.repo;
20
+ const tree = treeDir(run, m);
21
+ if (existsSync(tree)) return tree;
22
+ if (existsSync(readyFile(run, m)))
23
+ throw new CatherdError("E_TREE", `the worktree of ${m} is gone: ${tree}`, {
24
+ fix: "restore the tree (git worktree add it back on the milestone's branch) or cancel the run",
25
+ });
26
+ return run.meta.repo;
27
+ }
@@ -3,11 +3,11 @@ import { budgetStatus, formatBudget, type BudgetStatus, type Spend } from "../do
3
3
  import { CatherdError, errorMessage, isCatherdError } from "../domain/errors.ts";
4
4
  import type { Workspace, WorkspaceStep } from "../domain/workspace.ts";
5
5
  import { withFileLock } from "../infra/filelock.ts";
6
- import { git, gitToplevel } from "../infra/git.ts";
6
+ import { gitToplevel, isAncestor } from "../infra/git.ts";
7
7
  import { writeTextAtomic } from "../infra/store.ts";
8
8
  import { spendOf } from "./budget.ts";
9
9
  import { pendingDispatches } from "./dispatches.ts";
10
- import { landedCommits, landedMilestones } from "./milestones.ts";
10
+ import { landedCommitOf, landedMilestones } from "./milestones.ts";
11
11
  import { readRecords, type Run, runPaths } from "./run-store.ts";
12
12
  import {
13
13
  findWorkspace,
@@ -123,14 +123,6 @@ export async function dependencyBlockers(
123
123
  return out;
124
124
  }
125
125
 
126
- /** The commit the ledger holds for `milestone`, its latest landing; null when it never landed. */
127
- function landedCommitOf(run: Run, milestone: string): string | null {
128
- const commits = landedCommits(run);
129
- const milestones = landedMilestones(run);
130
- const at = milestones.lastIndexOf(milestone);
131
- return at < 0 ? null : (commits[at] ?? null);
132
- }
133
-
134
126
  /**
135
127
  * A `release: "merge"` step releases its dependents once its landed commit is an ancestor of its base ref
136
128
  * (`git merge-base --is-ancestor`), in the repository as it stands: catherd never fetches or polls.
@@ -139,9 +131,9 @@ async function unmerged(step: WorkspaceStep, child: Run): Promise<string | null>
139
131
  if (step.release !== "merge" || !step.base) return null;
140
132
  const commit = landedCommitOf(child, step.milestone);
141
133
  if (!commit) return `${step.id} has no landed commit for ${step.milestone} in its ledger`;
142
- const r = await git(child.meta.repo, ["merge-base", "--is-ancestor", commit, step.base]);
143
- if (r.kind === "ok") return null;
144
- if (r.kind === "failed" && r.exit === 1)
134
+ const merged = await isAncestor(child.meta.repo, commit, step.base);
135
+ if (merged === true) return null;
136
+ if (merged === false)
145
137
  return `${step.id}'s ${step.milestone} (${commit.slice(0, 7)}) is not merged into ${step.base} yet: merge it, git fetch, then ask again`;
146
138
  return `${step.id}: git cannot tell whether ${commit.slice(0, 7)} is in ${step.base} (does the ref exist in ${child.meta.repo}?)`;
147
139
  }
@@ -6,7 +6,10 @@ import { CatherdError } from "../domain/errors.ts";
6
6
  import { WORKSPACE_LIMIT, WorkspaceIdSchema, WorkspaceSchema, type Workspace } from "../domain/workspace.ts";
7
7
  import { withFileLock } from "../infra/filelock.ts";
8
8
  import { gitToplevel } from "../infra/git.ts";
9
+ import { indexPath } from "../infra/paths.ts";
10
+ import { readIndexFile } from "../infra/slopalytics.ts";
9
11
  import { readVersioned, writeTextAtomic } from "../infra/store.ts";
12
+ import { writeRunIndex } from "./catalog-service.ts";
10
13
  import { listDispatches } from "./dispatches.ts";
11
14
  import { landedCommits, landedMilestones } from "./milestones.ts";
12
15
  import { assertNotPaused, pausesOver } from "./pause.ts";
@@ -193,6 +196,8 @@ async function childFor(
193
196
  workspace: { id: workspace.id, step: step.id },
194
197
  });
195
198
  writePin(deps, run);
199
+ // pinned like startRun does, so a pair the machine index drops later never breaks the child midway
200
+ writeRunIndex(run.dir, readIndexFile(indexPath()));
196
201
  }
197
202
  const contract = existsSync(contractFile) ? join(run.dir, "workspace-contract.md") : null;
198
203
  if (contract && !existsSync(contract)) writeTextAtomic(contract, readFileSync(contractFile, "utf8"));
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{$ as t,Sn as n,xn as r}from"./react-query.gen-Bu2yV-qJ.js";import{u as i}from"./index-B6GiwDkh.js";var a=e(n(),1),o=`heroui-theme`,s=`(prefers-color-scheme: dark)`;function c(e){if(typeof window>`u`)return()=>{};let t=window.matchMedia(s);return t.addEventListener(`change`,e),()=>t.removeEventListener(`change`,e)}function l(){return window.matchMedia?.(s).matches?`dark`:`light`}function u(){}function d(e,t){t!==e&&(t&&document.documentElement.classList.remove(t),document.documentElement.classList.add(e),document.documentElement.setAttribute(`data-theme`,e))}function f(e=`system`){let[t,n]=(0,a.useState)(()=>typeof window>`u`?e:localStorage.getItem(o)??e),r=(0,a.useSyncExternalStore)(c,l,u),s=t===`system`?r:t,f=(0,a.useRef)(void 0);return i(()=>{s&&(d(s,f.current),f.current=s)},[s]),{resolvedTheme:s,setTheme:(0,a.useCallback)(e=>{typeof window<`u`&&(localStorage.setItem(o,e),n(e))},[]),theme:t}}var p=r();function m({className:e}){return(0,p.jsxs)(`svg`,{viewBox:`0 0 20 20`,"aria-hidden":`true`,className:t(`size-5 shrink-0`,e),children:[(0,p.jsx)(`rect`,{x:`2`,y:`12`,width:`16`,height:`4`,className:`fill-foreground`}),(0,p.jsx)(`rect`,{x:`3`,y:`6`,width:`3`,height:`3`,className:`fill-accent`}),(0,p.jsx)(`rect`,{x:`8.5`,y:`6`,width:`3`,height:`3`,className:`fill-ink-faint`}),(0,p.jsx)(`rect`,{x:`14`,y:`6`,width:`3`,height:`3`,className:`fill-ink-faint`})]})}function h({className:e}){return(0,p.jsxs)(`span`,{className:t(`inline-flex items-center gap-2`,e),children:[(0,p.jsx)(m,{}),(0,p.jsx)(`span`,{className:`text-[15px] font-semibold tracking-[-0.02em]`,children:`catherd`})]})}export{f as n,h as t};
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{Dt as t,G as n,K as r,Lt as i,Ot as a,Sn as o,Vt as s,W as c,X as l,_t as u,ct as d,dt as f,ft as p,ut as m}from"./react-query.gen-Bu2yV-qJ.js";import{$ as h,Q as g,X as _,Z as v,bt as y,ft as b,m as x,nt as S,ot as C,p as w,pt as T,st as E,tt as D,vt as O}from"./use-css-variable-CpnN9WtA.js";import{D as k}from"./label-DFSYBc62.js";var A=e(o(),1),j=typeof document<`u`&&window.visualViewport;function M(){let e=t(),n=(0,A.useRef)(null),[r,a]=(0,A.useState)(()=>e?{width:0,height:0}:N());return(0,A.useEffect)(()=>{let e=e=>{a(t=>e.width===t.width&&e.height===t.height?t:e)},t=()=>{j&&j.scale>1||e(N())},r=t=>{j&&j.scale>1||k(u(t))&&(n.current=O(t=>{t||e({width:document.documentElement.clientWidth,height:document.documentElement.clientHeight})}))};return e(N()),s()&&i()&&window.addEventListener(`blur`,r,!0),j?j.addEventListener(`resize`,t):window.addEventListener(`resize`,t),()=>{n.current?.(),s()&&i()&&window.removeEventListener(`blur`,r,!0),j?j.removeEventListener(`resize`,t):window.removeEventListener(`resize`,t)}},[]),r}function N(){return{width:j?Math.min(j.width*j.scale,document.documentElement.clientWidth):document.documentElement.clientWidth,height:j?j.height*j.scale:document.documentElement.clientHeight}}function P(e,t,n){let{overlayProps:r,underlayProps:i}=S({...e,isOpen:t.isOpen,onClose:t.close},n);D({isDisabled:!t.isOpen,UNSTABLE_overrideFocus:e.UNSTABLE_overrideFocus}),v();let{isEntering:a}=e;return(0,A.useEffect)(()=>{if(t.isOpen&&n.current)return a?C(n.current):E([n.current],{shouldUseInert:!0})},[t.isOpen,a,n]),{modalProps:f(r),underlayProps:i}}var F=(0,A.createContext)(null),I=(0,A.createContext)(null),L=(0,A.forwardRef)(function(e,t){if((0,A.useContext)(I))return A.createElement(H,{...e,modalRef:t},e.children);let{isDismissable:n,isKeyboardDismissDisabled:r,isOpen:i,defaultOpen:a,onOpenChange:o,children:s,isEntering:c,isExiting:l,UNSTABLE_portalContainer:u,shouldCloseOnInteractOutside:d,...f}=e;return A.createElement(z,{isDismissable:n,isKeyboardDismissDisabled:r,isOpen:i,defaultOpen:a,onOpenChange:o,isEntering:c,isExiting:l,UNSTABLE_portalContainer:u,shouldCloseOnInteractOutside:d},A.createElement(H,{...f,modalRef:t},s))});function R(e,r){[e,r]=n(e,r,F);let i=(0,A.useContext)(x),a=_(e),o=e.isOpen!=null||e.defaultOpen!=null||!i?a:i,s=d(r),c=(0,A.useRef)(null),l=b(s,o.isOpen,e.onExit),u=(0,A.useRef)(null),f=b(c,o.isOpen,e=>u.current?.(e)),p=l||f||e.isExiting||!1,m=t();return!o.isOpen&&!p||m?null:A.createElement(B,{...e,state:o,isExiting:p,overlayRef:s,modalRef:c,onExitRef:u})}var z=(0,A.forwardRef)(R);function B({UNSTABLE_portalContainer:e,UNSTABLE_deferUntilEntered:t,...n}){let i=n.modalRef,{state:o}=n,[s,u]=(0,A.useState)(!1),d=T(n.overlayRef,s,n.onEnter)||n.isEntering||!1,p=!!t&&(!s||d),{modalProps:h,underlayProps:_}=P({...n,isEntering:p},o,i),v=(0,A.useContext)(w),b=t?V(v,{isEntering:p}):v,S=r({...n,defaultClassName:`react-aria-ModalOverlay`,values:{isEntering:d,isExiting:n.isExiting,state:o}}),C=M(),E,D;if(typeof document<`u`){let e=y(document.body)?document.body:document.scrollingElement||document.documentElement,t=e.getBoundingClientRect().width%1,n=e.getBoundingClientRect().height%1;E=e.scrollWidth-t,D=e.scrollHeight-n}let k={...S.style,"--visual-viewport-width":C.width+`px`,"--visual-viewport-height":C.height+`px`,"--page-width":E===void 0?void 0:E+`px`,"--page-height":D===void 0?void 0:D+`px`};return a(()=>O(()=>u(!0)),[]),A.createElement(g,{isExiting:n.isExiting,portalContainer:e},A.createElement(l.div,{...f(m(n,{global:!0}),_),...S,style:k,ref:n.overlayRef,"data-entering":d||void 0,"data-exiting":n.isExiting||void 0},A.createElement(c,{values:[[I,{modalProps:h,modalRef:i,isExiting:n.isExiting,onExitRef:n.onExitRef,isOpen:s,isDismissable:n.isDismissable}],[x,o],[w,b]]},S.children)))}function V(e,t){if(e&&`slots`in e&&e.slots){let n={};for(let r in e.slots)n[r]={...e.slots[r],...t};return{...e,slots:n}}return{...e,...t}}function H(e){let{modalProps:t,modalRef:n,isExiting:i,onExitRef:o,isDismissable:s,isOpen:c}=(0,A.useContext)(I),u=(0,A.useContext)(x),g=(0,A.useMemo)(()=>p(e.modalRef,n),[e.modalRef,n]),_=d(g),v=T(_,c,e.onEnter),y=r({...e,defaultClassName:`react-aria-Modal`,values:{isEntering:v,isExiting:i,state:u}});return a(()=>{o&&(o.current=e.onExit)}),A.createElement(l.div,{...f(m(e,{global:!0}),t),...y,ref:_,"data-entering":v||void 0,"data-exiting":i||void 0},s&&A.createElement(h,{onDismiss:u.close}),y.children)}export{z as n,L as t};
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-XKyaHtl9.js";import{C as t,O as n,Sn as r,ct as i,dt as a,ft as o}from"./react-query.gen-Bu2yV-qJ.js";var s=e(r(),1),c=s.forwardRef(({children:e,...r},c)=>{c=i(c);let{pressProps:l}=t({...r,ref:c}),{focusableProps:u}=n(r,c),d=s.Children.only(e);(0,s.useEffect)(()=>{},[c,r.isDisabled]);let f=d.props.ref;return s.cloneElement(d,{...a(l,u,d.props),ref:o(f,c)})});export{c as t};