catherd-cli 1.4.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 (320) hide show
  1. package/README.md +188 -169
  2. package/THIRD_PARTY_NOTICES.md +217 -35
  3. package/catalog/jev.json +27 -1
  4. package/catalog/models.json +56 -239
  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 -356
  11. package/plugin/skills/catherd-setup/SKILL.md +21 -20
  12. package/src/adapters/all.ts +0 -4
  13. package/src/adapters/backend.ts +19 -4
  14. package/src/adapters/claude-code/index.ts +126 -14
  15. package/src/adapters/codex/events.ts +10 -0
  16. package/src/adapters/codex/index.ts +17 -5
  17. package/src/adapters/cursor/home.ts +1 -1
  18. package/src/adapters/cursor/index.ts +10 -4
  19. package/src/adapters/opencode/index.ts +38 -1
  20. package/src/cli.ts +27 -27
  21. package/src/domain/agents.ts +3 -1
  22. package/src/domain/bars.ts +33 -107
  23. package/src/domain/blocked.ts +72 -0
  24. package/src/domain/branch.ts +51 -0
  25. package/src/domain/brief.ts +106 -0
  26. package/src/domain/catalog.ts +69 -313
  27. package/src/domain/changes.ts +23 -1
  28. package/src/domain/cost.ts +19 -29
  29. package/src/domain/errors.ts +6 -2
  30. package/src/domain/fail-lines.ts +26 -0
  31. package/src/domain/failover.ts +37 -75
  32. package/src/domain/findings.ts +47 -0
  33. package/src/domain/gate-brief.ts +53 -0
  34. package/src/domain/hints.ts +13 -2
  35. package/src/domain/ids.ts +13 -2
  36. package/src/domain/jev.ts +47 -2
  37. package/src/domain/judge.ts +124 -0
  38. package/src/domain/lane.ts +118 -3
  39. package/src/domain/milestone-memory.ts +53 -0
  40. package/src/domain/notice.ts +32 -8
  41. package/src/domain/notify.ts +32 -0
  42. package/src/domain/plan-doc.ts +198 -0
  43. package/src/domain/plan-lint.ts +141 -0
  44. package/src/domain/profile-rules.ts +175 -138
  45. package/src/domain/profile.ts +140 -63
  46. package/src/domain/record.ts +68 -3
  47. package/src/domain/reply.ts +29 -0
  48. package/src/domain/role-prompts.ts +182 -56
  49. package/src/domain/role-scope.ts +76 -0
  50. package/src/domain/role-tools.ts +34 -0
  51. package/src/domain/roles.ts +2 -0
  52. package/src/domain/route.ts +90 -0
  53. package/src/domain/rules.ts +33 -0
  54. package/src/domain/run-events.ts +298 -0
  55. package/src/domain/run-machine.ts +668 -0
  56. package/src/domain/run-state.ts +1310 -0
  57. package/src/domain/scorecard.ts +197 -0
  58. package/src/domain/select.ts +229 -72
  59. package/src/domain/slopalytics.ts +72 -0
  60. package/src/domain/state.ts +5 -0
  61. package/src/domain/util.ts +22 -0
  62. package/src/domain/workspace.ts +130 -0
  63. package/src/entry/answer-command.ts +39 -0
  64. package/src/entry/catalog-command.ts +24 -165
  65. package/src/entry/check-bin.ts +17 -0
  66. package/src/entry/cli-kit.ts +18 -2
  67. package/src/entry/daemon-bin.ts +22 -0
  68. package/src/entry/deps.ts +2 -0
  69. package/src/entry/doctor-command.ts +20 -2
  70. package/src/entry/glyphs.ts +15 -2
  71. package/src/entry/host-arg.ts +28 -0
  72. package/src/entry/init-command.ts +20 -69
  73. package/src/entry/knowledge-command.ts +121 -5
  74. package/src/entry/lock-command.ts +103 -8
  75. package/src/entry/mcp/handshake.ts +6 -2
  76. package/src/entry/mcp/role-bin.ts +20 -0
  77. package/src/entry/mcp/role-server.ts +134 -0
  78. package/src/entry/mcp/server.ts +85 -21
  79. package/src/entry/mcp/session-tools.ts +149 -0
  80. package/src/entry/panel/openapi.ts +189 -0
  81. package/src/entry/panel/server.ts +410 -0
  82. package/src/entry/panel-command.ts +68 -0
  83. package/src/entry/pause-command.ts +67 -0
  84. package/src/entry/profile-command.ts +33 -18
  85. package/src/entry/role-cli-command.ts +159 -0
  86. package/src/entry/runs-command.ts +81 -27
  87. package/src/entry/scorecard-command.ts +187 -0
  88. package/src/entry/supervise-bin.ts +1 -1
  89. package/src/entry/supervise-command.ts +30 -3
  90. package/src/entry/workspace-command.ts +44 -0
  91. package/src/infra/boot-lock.ts +25 -0
  92. package/src/infra/delivery.ts +3 -0
  93. package/src/infra/dispatch-dir.ts +2 -0
  94. package/src/infra/env.ts +11 -3
  95. package/src/infra/git.ts +70 -0
  96. package/src/infra/heavy-lock.ts +133 -6
  97. package/src/infra/host-probe.ts +16 -0
  98. package/src/infra/launch.ts +63 -7
  99. package/src/infra/lock-activity.ts +88 -0
  100. package/src/infra/login-env.ts +48 -0
  101. package/src/infra/memory.ts +45 -0
  102. package/src/infra/ntfy.ts +40 -0
  103. package/src/infra/open-url.ts +42 -0
  104. package/src/infra/paths.ts +37 -2
  105. package/src/infra/role-mcp.ts +69 -0
  106. package/src/infra/slopalytics.ts +30 -0
  107. package/src/infra/store.ts +6 -1
  108. package/src/infra/supervisor.ts +107 -16
  109. package/src/services/admission.ts +133 -31
  110. package/src/services/backends.ts +4 -6
  111. package/src/services/budget.ts +26 -6
  112. package/src/services/capture.ts +1 -22
  113. package/src/services/catalog-service.ts +155 -254
  114. package/src/services/credentials.ts +15 -26
  115. package/src/services/daemon.ts +181 -0
  116. package/src/services/dispatch-service.ts +209 -17
  117. package/src/services/dispatches.ts +30 -2
  118. package/src/services/doctor-access.ts +8 -3
  119. package/src/services/doctor-backends.ts +1 -1
  120. package/src/services/doctor-checks.ts +47 -0
  121. package/src/services/doctor-docker.ts +221 -0
  122. package/src/services/doctor-role-mcp.ts +65 -0
  123. package/src/services/doctor.ts +72 -15
  124. package/src/services/end-push.ts +157 -0
  125. package/src/services/failover-rungs.ts +109 -0
  126. package/src/services/finalize.ts +24 -2
  127. package/src/services/gate-env.ts +217 -0
  128. package/src/services/gate-service.ts +231 -45
  129. package/src/services/global-install.ts +46 -13
  130. package/src/services/jev-service.ts +2 -4
  131. package/src/services/lane-edit.ts +154 -0
  132. package/src/services/lane-service.ts +291 -55
  133. package/src/services/milestone-check.ts +433 -0
  134. package/src/services/milestone-tree.ts +170 -0
  135. package/src/services/milestones.ts +167 -29
  136. package/src/services/notifier.ts +124 -4
  137. package/src/services/notify-service.ts +26 -0
  138. package/src/services/orchestrator-wait.ts +82 -0
  139. package/src/services/panel-setup.ts +116 -0
  140. package/src/services/pause.ts +161 -0
  141. package/src/services/peek.ts +79 -17
  142. package/src/services/plan-input.ts +151 -0
  143. package/src/services/ports.ts +60 -5
  144. package/src/services/preflight.ts +232 -24
  145. package/src/services/profile-service.ts +31 -3
  146. package/src/services/profile-store.ts +18 -6
  147. package/src/services/protocol.ts +123 -25
  148. package/src/services/provenance.ts +34 -89
  149. package/src/services/questions.ts +24 -0
  150. package/src/services/reconcile.ts +33 -1
  151. package/src/services/reentry.ts +7 -6
  152. package/src/services/role-access.ts +27 -0
  153. package/src/services/routing-service.ts +161 -42
  154. package/src/services/run-briefs.ts +477 -0
  155. package/src/services/run-client.ts +217 -0
  156. package/src/services/run-effects.ts +837 -0
  157. package/src/services/run-events-store.ts +174 -0
  158. package/src/services/run-evidence.ts +1 -1
  159. package/src/services/run-pin.ts +125 -0
  160. package/src/services/run-read.ts +221 -0
  161. package/src/services/run-report.ts +176 -0
  162. package/src/services/run-service.ts +204 -23
  163. package/src/services/run-store.ts +159 -18
  164. package/src/services/scorecard-metrics.ts +86 -0
  165. package/src/services/scorecard.ts +382 -0
  166. package/src/services/scratch.ts +60 -0
  167. package/src/services/sessions.ts +3 -1
  168. package/src/services/state.ts +21 -0
  169. package/src/services/summary.ts +76 -12
  170. package/src/services/tree-path.ts +27 -0
  171. package/src/services/verifier-brief.ts +23 -0
  172. package/src/services/verifier-step.ts +48 -0
  173. package/src/services/workspace-admission.ts +259 -0
  174. package/src/services/workspace-service.ts +259 -0
  175. package/src/services/workspace-store.ts +95 -0
  176. package/web/dist/assets/Brand-PKlUX5TE.js +1 -0
  177. package/web/dist/assets/Brand-PKlUX5TE.js.br +0 -0
  178. package/web/dist/assets/Brand-PKlUX5TE.js.gz +0 -0
  179. package/web/dist/assets/Modal-Dxc5so53.js +1 -0
  180. package/web/dist/assets/Modal-Dxc5so53.js.br +0 -0
  181. package/web/dist/assets/Modal-Dxc5so53.js.gz +0 -0
  182. package/web/dist/assets/Pressable-C8-T6Ed_.js +1 -0
  183. package/web/dist/assets/StatusChip-B5xfI_Mn.js +153 -0
  184. package/web/dist/assets/StatusChip-B5xfI_Mn.js.br +0 -0
  185. package/web/dist/assets/StatusChip-B5xfI_Mn.js.gz +0 -0
  186. package/web/dist/assets/_app-Ea5cpyKe.js +1 -0
  187. package/web/dist/assets/_app-Ea5cpyKe.js.br +0 -0
  188. package/web/dist/assets/_app-Ea5cpyKe.js.gz +0 -0
  189. package/web/dist/assets/_runId-pTG0fXVo.js +1 -0
  190. package/web/dist/assets/_runId-pTG0fXVo.js.br +0 -0
  191. package/web/dist/assets/_runId-pTG0fXVo.js.gz +0 -0
  192. package/web/dist/assets/drawer-Du3cdlok.js +1 -0
  193. package/web/dist/assets/drawer-Du3cdlok.js.br +0 -0
  194. package/web/dist/assets/drawer-Du3cdlok.js.gz +0 -0
  195. package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  196. package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  197. package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  198. package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  199. package/web/dist/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  200. package/web/dist/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  201. package/web/dist/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  202. package/web/dist/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  203. package/web/dist/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  204. package/web/dist/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  205. package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  206. package/web/dist/assets/index-B6GiwDkh.js +11 -0
  207. package/web/dist/assets/index-B6GiwDkh.js.br +0 -0
  208. package/web/dist/assets/index-B6GiwDkh.js.gz +0 -0
  209. package/web/dist/assets/index-C5IbylvM.css +2 -0
  210. package/web/dist/assets/index-C5IbylvM.css.br +0 -0
  211. package/web/dist/assets/index-C5IbylvM.css.gz +0 -0
  212. package/web/dist/assets/label-DFSYBc62.js +1 -0
  213. package/web/dist/assets/label-DFSYBc62.js.br +0 -0
  214. package/web/dist/assets/label-DFSYBc62.js.gz +0 -0
  215. package/web/dist/assets/link-D8EcKZUt.js +1 -0
  216. package/web/dist/assets/link-D8EcKZUt.js.br +0 -0
  217. package/web/dist/assets/link-D8EcKZUt.js.gz +0 -0
  218. package/web/dist/assets/login-DaN5HPPW.js +1 -0
  219. package/web/dist/assets/login-DaN5HPPW.js.br +0 -0
  220. package/web/dist/assets/login-DaN5HPPW.js.gz +0 -0
  221. package/web/dist/assets/query-Cu--ccT3.js +1 -0
  222. package/web/dist/assets/questions-shoO9OVj.js +1 -0
  223. package/web/dist/assets/questions-shoO9OVj.js.br +0 -0
  224. package/web/dist/assets/questions-shoO9OVj.js.gz +0 -0
  225. package/web/dist/assets/react-query.gen-Bu2yV-qJ.js +12 -0
  226. package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.br +0 -0
  227. package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.gz +0 -0
  228. package/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  229. package/web/dist/assets/run-status-C0DRuV7U.js +1 -0
  230. package/web/dist/assets/runs-C3rg2zZc.js +1 -0
  231. package/web/dist/assets/runs-C3rg2zZc.js.br +0 -0
  232. package/web/dist/assets/runs-C3rg2zZc.js.gz +0 -0
  233. package/web/dist/assets/setup-5I4Vl9fR.js +1 -0
  234. package/web/dist/assets/setup-5I4Vl9fR.js.br +0 -0
  235. package/web/dist/assets/setup-5I4Vl9fR.js.gz +0 -0
  236. package/web/dist/assets/table-BOxXhqS4.js +1 -0
  237. package/web/dist/assets/table-BOxXhqS4.js.br +0 -0
  238. package/web/dist/assets/table-BOxXhqS4.js.gz +0 -0
  239. package/web/dist/assets/textfield-BzRFkxBF.js +1 -0
  240. package/web/dist/assets/textfield-BzRFkxBF.js.br +0 -0
  241. package/web/dist/assets/textfield-BzRFkxBF.js.gz +0 -0
  242. package/web/dist/assets/time--9kSQbbp.js +1 -0
  243. package/web/dist/assets/use-css-variable-CpnN9WtA.js +5 -0
  244. package/web/dist/assets/use-css-variable-CpnN9WtA.js.br +0 -0
  245. package/web/dist/assets/use-css-variable-CpnN9WtA.js.gz +0 -0
  246. package/web/dist/assets/useMutation-CVPKlR34.js +1 -0
  247. package/web/dist/assets/useMutation-CVPKlR34.js.br +0 -0
  248. package/web/dist/assets/useMutation-CVPKlR34.js.gz +0 -0
  249. package/web/dist/index.html +15 -0
  250. package/catalog/ATTRIBUTION.md +0 -36
  251. package/catalog/scores.json +0 -3202
  252. package/catalog/sources.json +0 -97
  253. package/src/adapters/antigravity/events.ts +0 -131
  254. package/src/adapters/antigravity/home.ts +0 -46
  255. package/src/adapters/antigravity/index.ts +0 -320
  256. package/src/adapters/antigravity/models.ts +0 -37
  257. package/src/adapters/grok/events.ts +0 -112
  258. package/src/adapters/grok/home.ts +0 -132
  259. package/src/adapters/grok/index.ts +0 -339
  260. package/src/adapters/grok/models.ts +0 -48
  261. package/src/domain/calibration.ts +0 -125
  262. package/src/domain/sources.ts +0 -144
  263. package/src/entry/mcp/dispatch-tools.ts +0 -52
  264. package/src/entry/mcp/lane-tools.ts +0 -84
  265. package/src/entry/mcp/protocol-tools.ts +0 -57
  266. package/src/entry/mcp/run-tools.ts +0 -148
  267. package/src/entry/mcp/setup-tools.ts +0 -108
  268. package/src/entry/tui/commands.ts +0 -563
  269. package/src/entry/tui/effects.ts +0 -381
  270. package/src/entry/tui/fixtures.ts +0 -637
  271. package/src/entry/tui/profile-edits.ts +0 -146
  272. package/src/entry/tui/profile-tree.ts +0 -429
  273. package/src/entry/tui/providers/app.tsx +0 -162
  274. package/src/entry/tui/providers/boundary.tsx +0 -57
  275. package/src/entry/tui/providers/data.tsx +0 -160
  276. package/src/entry/tui/providers/keymap.tsx +0 -166
  277. package/src/entry/tui/providers/root.tsx +0 -45
  278. package/src/entry/tui/providers/theme.tsx +0 -17
  279. package/src/entry/tui/providers/toast.tsx +0 -77
  280. package/src/entry/tui/run.tsx +0 -114
  281. package/src/entry/tui/state.ts +0 -373
  282. package/src/entry/tui/stories.ts +0 -114
  283. package/src/entry/tui/text.ts +0 -130
  284. package/src/entry/tui/theme.ts +0 -140
  285. package/src/entry/tui/views/app.tsx +0 -208
  286. package/src/entry/tui/views/dialogs.tsx +0 -41
  287. package/src/entry/tui/views/profile-actions.ts +0 -331
  288. package/src/entry/tui/views/profiles.tsx +0 -520
  289. package/src/entry/tui/views/runs.tsx +0 -573
  290. package/src/entry/tui/views/save-dialog.tsx +0 -340
  291. package/src/entry/tui/views/status.tsx +0 -209
  292. package/src/entry/tui/widgets/chrome.tsx +0 -114
  293. package/src/entry/tui/widgets/dialog-confirm.tsx +0 -58
  294. package/src/entry/tui/widgets/dialog-prompt.tsx +0 -52
  295. package/src/entry/tui/widgets/dialog-select.tsx +0 -212
  296. package/src/entry/tui/widgets/dialog.tsx +0 -68
  297. package/src/entry/tui/widgets/line.tsx +0 -92
  298. package/src/entry/tui/widgets/list.tsx +0 -197
  299. package/src/entry/tui/widgets/spinner.tsx +0 -49
  300. package/src/entry/tui/widgets/toast.tsx +0 -62
  301. package/src/infra/sources/arena.ts +0 -87
  302. package/src/infra/sources/artificial-analysis.ts +0 -112
  303. package/src/infra/sources/cache.ts +0 -124
  304. package/src/infra/sources/csv.ts +0 -38
  305. package/src/infra/sources/epoch.ts +0 -82
  306. package/src/infra/sources/http.ts +0 -75
  307. package/src/infra/sources/litellm.ts +0 -58
  308. package/src/infra/sources/models-dev.ts +0 -59
  309. package/src/infra/sources/openrouter-endpoints.ts +0 -52
  310. package/src/infra/sources/openrouter-models.ts +0 -60
  311. package/src/infra/sources/rows.ts +0 -38
  312. package/src/infra/sources/vectara.ts +0 -69
  313. package/src/infra/sources/zip.ts +0 -48
  314. package/src/services/catalog-refresh.ts +0 -201
  315. package/src/services/doctor-sources.ts +0 -76
  316. package/src/services/runs-page.ts +0 -255
  317. package/src/services/source-derive.ts +0 -374
  318. package/src/services/source-sync.ts +0 -305
  319. package/src/services/standins.ts +0 -204
  320. package/src/services/treat-likes.ts +0 -97
@@ -1,125 +0,0 @@
1
- import type { Dim } from "./catalog.ts";
2
- import type { SourceId } from "./sources.ts";
3
-
4
- /** Spec 1.2 §4.2: a source needs this many rungs shared with the anchor for a dimension. */
5
- export const MIN_SHARED = 5;
6
- /** Spec 1.2 §4.2: a fit below this R² is not used. */
7
- export const MIN_R2 = 0.5;
8
-
9
- /** One number a source parser returns: `field` of `source`, and the benchmark it names. */
10
- export interface FieldRef {
11
- source: SourceId;
12
- field: string;
13
- benchmark: string;
14
- }
15
-
16
- /**
17
- * Spec 1.2 §4.1: each dimension's anchor, the unit its bars are in, and the sources calibrated onto it.
18
- * `shipped` is the hand-typed value `catalog/scores.json` carries, published for that rung (every anchor is
19
- * keyless, spec 1.2 §4.1).
20
- */
21
- export const DIM_SOURCES: Record<Dim, { anchor: FieldRef | "shipped"; others: FieldRef[] }> = {
22
- repo_code: {
23
- anchor: "shipped",
24
- others: [
25
- {
26
- source: "artificial-analysis",
27
- field: "livecodebench",
28
- benchmark: "LiveCodeBench (Artificial Analysis)",
29
- },
30
- { source: "artificial-analysis", field: "scicode", benchmark: "SciCode (Artificial Analysis)" },
31
- {
32
- source: "artificial-analysis",
33
- field: "artificial_analysis_coding_index",
34
- benchmark: "Artificial Analysis Coding Index",
35
- },
36
- { source: "epoch", field: "frontiercode", benchmark: "FrontierCode (Epoch AI)" },
37
- ],
38
- },
39
- // plan 14 Ruling C-2: the vendors' Terminal-Bench 4.0 values, not Epoch's Terminal-Bench 2.0, which shares
40
- // no rung with them; Epoch is fitted onto them once it covers five of their rungs
41
- terminal: {
42
- anchor: "shipped",
43
- others: [
44
- { source: "epoch", field: "terminalbench", benchmark: "Terminal-Bench (Epoch AI)" },
45
- {
46
- source: "artificial-analysis",
47
- field: "terminalbench_v2_1",
48
- benchmark: "Terminal-Bench 2.1 (Artificial Analysis)",
49
- },
50
- ],
51
- },
52
- honesty: {
53
- anchor: "shipped",
54
- others: [
55
- { source: "vectara", field: "factual_consistency", benchmark: "Vectara factual consistency rate" },
56
- { source: "arena", field: "agent_tool_hallucination", benchmark: "Arena agent tool hallucination" },
57
- ],
58
- },
59
- agentic: {
60
- anchor: { source: "arena", field: "agent", benchmark: "Arena agent, net improvement" },
61
- others: [
62
- { source: "arena", field: "agent_task_outcome_explicit", benchmark: "Arena agent task outcome" },
63
- { source: "arena", field: "agent_bash_recovery_steps", benchmark: "Arena agent bash recovery" },
64
- { source: "artificial-analysis", field: "tau2", benchmark: "tau2-bench (Artificial Analysis)" },
65
- ],
66
- },
67
- steer: {
68
- anchor: { source: "arena", field: "agent_steerability", benchmark: "Arena agent steerability" },
69
- others: [],
70
- },
71
- frontend: {
72
- anchor: { source: "arena", field: "webdev", benchmark: "Arena WebDev rating" },
73
- others: [{ source: "epoch", field: "webdev", benchmark: "WebDev Arena (Epoch AI)" }],
74
- },
75
- };
76
-
77
- export interface LineFit {
78
- a: number;
79
- b: number;
80
- r2: number;
81
- n: number;
82
- }
83
-
84
- /** Least squares `anchor = a·x + b` over `[x, anchor]` pairs; null with fewer than 2 pairs or no spread in x. */
85
- export function fitLine(pairs: readonly (readonly [number, number])[]): LineFit | null {
86
- const n = pairs.length;
87
- if (n < 2) return null;
88
- const mx = pairs.reduce((s, [x]) => s + x, 0) / n;
89
- const my = pairs.reduce((s, [, y]) => s + y, 0) / n;
90
- let sxx = 0;
91
- let sxy = 0;
92
- let syy = 0;
93
- for (const [x, y] of pairs) {
94
- sxx += (x - mx) ** 2;
95
- sxy += (x - mx) * (y - my);
96
- syy += (y - my) ** 2;
97
- }
98
- if (sxx === 0) return null;
99
- const a = sxy / sxx;
100
- const b = my - a * mx;
101
- const res = pairs.reduce((s, [x, y]) => s + (y - (a * x + b)) ** 2, 0);
102
- return { a, b, r2: syy === 0 ? 0 : 1 - res / syy, n };
103
- }
104
-
105
- /**
106
- * Spec 1.2 §4.2: the fit of `other` onto `anchor` over the keys both hold, and whether it may be used: at
107
- * least MIN_SHARED shared rungs and an R² of at least MIN_R2. `why` says why not.
108
- */
109
- export function calibrate(
110
- anchor: ReadonlyMap<string, number>,
111
- other: ReadonlyMap<string, number>,
112
- ): { fit: LineFit | null; n: number; used: boolean; why?: string } {
113
- const pairs: [number, number][] = [];
114
- for (const [k, x] of other) {
115
- const y = anchor.get(k);
116
- if (y !== undefined) pairs.push([x, y]);
117
- }
118
- const n = pairs.length;
119
- if (n < MIN_SHARED)
120
- return { fit: null, n, used: false, why: `${n} shared rungs; a fit needs ${MIN_SHARED}` };
121
- const fit = fitLine(pairs);
122
- if (!fit) return { fit: null, n, used: false, why: "the source gives every shared rung the same value" };
123
- if (fit.r2 < MIN_R2) return { fit, n, used: false, why: `R² ${fit.r2.toFixed(2)} is below ${MIN_R2}` };
124
- return { fit, n, used: true };
125
- }
@@ -1,144 +0,0 @@
1
- import { z } from "zod";
2
- import { DIMS, type Family, FamilyFactsSchema, ScoreSchema } from "./catalog.ts";
3
-
4
- /** Spec 1.2 §3.1: the sources `catherd catalog sync` reads, one cache file each in `<data>/sources/`. */
5
- export const SOURCE_IDS = [
6
- "models-dev",
7
- "openrouter-models",
8
- "openrouter-endpoints",
9
- "litellm",
10
- "arena",
11
- "vectara",
12
- "epoch",
13
- "artificial-analysis",
14
- ] as const;
15
- export type SourceId = (typeof SOURCE_IDS)[number];
16
-
17
- /** `catalog/sources.json`: each source with its license and attribution line, the id aliases, default efforts. */
18
- export const SourcesFileSchema = z.looseObject({
19
- schema: z.literal(1),
20
- version: z.string(),
21
- sources: z.array(
22
- z.object({
23
- id: z.enum(SOURCE_IDS),
24
- name: z.string(),
25
- url: z.url(),
26
- license: z.string(),
27
- attribution: z.string(),
28
- keyed: z.boolean(),
29
- }),
30
- ),
31
- /** a source's irregular model id → the catalog's id (spec 1.2 §3.4) */
32
- aliases: z.record(z.string(), z.string()),
33
- /** family id → the effort a source that names none means; a family not listed means `high` */
34
- defaultEffort: z.record(z.string(), z.string()),
35
- });
36
- export type SourcesFile = z.infer<typeof SourcesFileSchema>;
37
-
38
- /** Effort words as catherd writes them, weakest first; a source's spelling (`xHigh`, `MAX`) is folded onto these. */
39
- export const EFFORT_ORDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"] as const;
40
-
41
- /** `s` as a catherd effort word (`xHigh` → `xhigh`), or null when it is not one. */
42
- export function effortWord(s: string): string | null {
43
- const w = s.trim().toLowerCase();
44
- return (EFFORT_ORDER as readonly string[]).includes(w) ? w : null;
45
- }
46
-
47
- /**
48
- * Spec 1.2 §3.4: a source's model id in catherd's form: without a `vendor/` prefix, lower case, and each run
49
- * of dots and blanks as one `-` (`openai/gpt-5.6-sol` and `GPT 5.6 Sol` → `gpt-5-6-sol`).
50
- */
51
- export function normalizeId(id: string): string {
52
- return id
53
- .slice(id.lastIndexOf("/") + 1)
54
- .trim()
55
- .toLowerCase()
56
- .replace(/[\s.]+/g, "-")
57
- .replace(/-+/g, "-");
58
- }
59
-
60
- /** A parser's `rung`: `<source's model id>#<effort>`, or the bare id when the source names no effort. */
61
- export function splitSourceRung(rung: string): { id: string; effort: string | null } {
62
- const hash = rung.lastIndexOf("#");
63
- return hash < 0 ? { id: rung, effort: null } : { id: rung.slice(0, hash), effort: rung.slice(hash + 1) };
64
- }
65
-
66
- export interface IdMapper {
67
- /** the id pairs are keyed by: normalized, then through the alias table */
68
- key(id: string): string;
69
- /** the catalog family a source id names, directly, by a backend's own id or through an alias; never guessed */
70
- family(id: string): Family | null;
71
- }
72
-
73
- /** Spec 1.2 §3.4: matches a source's ids to the catalog's families. */
74
- export function idMapper(families: Family[], aliases: Record<string, string>): IdMapper {
75
- const alias = new Map(Object.entries(aliases).map(([from, to]) => [normalizeId(from), normalizeId(to)]));
76
- const byKey = new Map<string, Family>();
77
- for (const f of families) {
78
- for (const on of Object.values(f.on)) if (on) byKey.set(normalizeId(on.id), f);
79
- byKey.set(normalizeId(f.id), f);
80
- }
81
- const key = (id: string) => {
82
- const n = normalizeId(id);
83
- return alias.get(n) ?? n;
84
- };
85
- return { key, family: (id) => byKey.get(key(id)) ?? null };
86
- }
87
-
88
- /** Spec 1.2 §3.4: the effort a source that names none means for `family`. */
89
- export const defaultEffortOf = (sources: SourcesFile, family: Family): string =>
90
- sources.defaultEffort[family.id] ?? "high";
91
-
92
- const order = (e: string) => (EFFORT_ORDER as readonly string[]).indexOf(e);
93
-
94
- /** Every effort the family offers on any backend, weakest first (spec 1.2 §4.3 `adjacent`). */
95
- export function familyEfforts(f: Family): string[] {
96
- const all = new Set<string>();
97
- for (const on of Object.values(f.on)) for (const e of on?.efforts ?? []) all.add(e);
98
- return [...all].filter((e) => order(e) >= 0).sort((a, b) => order(a) - order(b));
99
- }
100
-
101
- /** Spec 1.2 §4.2: one source's fit onto a dimension's anchor, used or not, and why not. */
102
- export const FitRowSchema = z.object({
103
- dim: z.enum(DIMS),
104
- source: z.string(),
105
- field: z.string(),
106
- n: z.number().int(),
107
- a: z.number().nullable(),
108
- b: z.number().nullable(),
109
- r2: z.number().nullable(),
110
- used: z.boolean(),
111
- why: z.string().optional(),
112
- });
113
- export type FitRow = z.infer<typeof FitRowSchema>;
114
-
115
- /**
116
- * `<data>/sources/derived.json`: what a sync made of the cached answers. `scores` are the synced values the
117
- * catalog layers over the shipped ones (`measured`, `calibrated`, `adjacent`), `facts` each family's facts,
118
- * `unmatched` each source's ids no family matched (never guessed), `warnings` the cross-checks that failed.
119
- */
120
- export const DerivedSchema = z.looseObject({
121
- schema: z.literal(1),
122
- builtAt: z.iso.datetime(),
123
- scores: z.array(ScoreSchema),
124
- facts: z.record(z.string(), FamilyFactsSchema),
125
- fits: z.array(FitRowSchema),
126
- unmatched: z.record(z.string(), z.array(z.string())),
127
- warnings: z.array(z.string()),
128
- /** canonical rung → the Artificial Analysis stand-in features (spec 1.2 §6.3); a file before plan 14 has none */
129
- features: z.record(z.string(), z.record(z.string(), z.number())).default({}),
130
- });
131
- export type Derived = z.infer<typeof DerivedSchema>;
132
-
133
- /** The effort of `have` nearest `target` (the weaker one on a tie); null when `have` holds no effort word. */
134
- export function nearestEffort(target: string, have: string[]): string | null {
135
- const t = order(target);
136
- let best: string | null = null;
137
- for (const e of have) {
138
- const i = order(e);
139
- if (i < 0 || t < 0) continue;
140
- const b = best === null ? Number.POSITIVE_INFINITY : Math.abs(order(best) - t);
141
- if (Math.abs(i - t) < b || (Math.abs(i - t) === b && i < order(best as string))) best = e;
142
- }
143
- return best;
144
- }
@@ -1,52 +0,0 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import { ID_PATTERN } from "../../domain/ids.ts";
4
- import { ROLES } from "../../domain/roles.ts";
5
- import { cancel, dispatch } from "../../services/dispatch-service.ts";
6
- import { peek } from "../../services/peek.ts";
7
- import type { Deps } from "../../services/ports.ts";
8
- import { handle } from "./result.ts";
9
-
10
- export function registerDispatchTools(server: McpServer, deps: Deps): void {
11
- server.registerTool(
12
- "dispatch",
13
- {
14
- description:
15
- "Start one role on a process backend (codex, claude-code, opencode) and return at launch, in about a second, with { dispatched: { name, role, rung, dispatchId, admittedAt }, hints }; the role runs on. When it finishes, catherd sends this session a <cross-session-message from-name=\"catherd\"> naming the run, the role and its status, and result(run, name) reads the record. Dispatch every independent role one after another, then end your turn. The brief is the text itself; dispatch appends the role's reply contract (reply length and the STATUS line), so do not write it. With lane, the lane file's Owns: paths guard against overlapping lanes, and a lane not yet routed is routed first (a rung off its routed ladder starts at the routed rung, with a hint). thread resumes that role's thread for its own fix round. Refused with a structured error (E_ADMIT_*, E_RUN_BUDGET, E_BACKEND_*) before anything starts. Call it from the main thread.",
16
- inputSchema: {
17
- run: z.string(),
18
- role: z.enum(ROLES),
19
- name: z.string().regex(ID_PATTERN),
20
- brief: z.string().min(1),
21
- rung: z.string().min(3),
22
- thread: z.string().optional(),
23
- lane: z.string().regex(ID_PATTERN).optional(),
24
- next: z.string().optional(),
25
- },
26
- },
27
- (a) => handle(() => dispatch(deps, a)),
28
- );
29
-
30
- server.registerTool(
31
- "peek",
32
- {
33
- description:
34
- "A look at the runs, without waiting: with run, that run (and this session becomes its owner); without, every run this session owns, else the newest. Per run, first the open owner questions, which are the owner's questions: push them to the owner, relay the owner's answer with answer(run, milestone, answer), and go on with the work that does not depend on them. Then each live role with its rung, seconds since it started and its last event (the last command, file edit or message line); every finished record not yet read, as the first line of catherd's message; the latest native Claude run; the run's next step; protocol, the milestone loop's next step and its six-line checklist; and verifier, the verifier's latest gate step. name narrows it to one role. It never marks a record read: result(run, name) does. Call it when the user asks how it is going, when a decision needs the other roles' state, or once after run_start on a resumed run; never in a loop.",
35
- inputSchema: {
36
- run: z.string().optional(),
37
- name: z.string().regex(ID_PATTERN).optional(),
38
- },
39
- },
40
- (a) => handle(() => peek(deps, a)),
41
- );
42
-
43
- server.registerTool(
44
- "cancel",
45
- {
46
- description:
47
- "Stop a live dispatch (interrupt, then SIGTERM, then SIGKILL) and return its record, marked cancelled and read, and hints.",
48
- inputSchema: { run: z.string(), name: z.string().regex(ID_PATTERN) },
49
- },
50
- (a) => handle(() => cancel(deps, a.run, a.name, { read: true })),
51
- );
52
- }
@@ -1,84 +0,0 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import { ID_PATTERN } from "../../domain/ids.ts";
4
- import { ROLES } from "../../domain/roles.ts";
5
- import { CLIMB_REASONS } from "../../domain/route.ts";
6
- import { ask, climb, LAND_SKIPS, land, route } from "../../services/lane-service.ts";
7
- import type { Deps } from "../../services/ports.ts";
8
- import { preflight } from "../../services/preflight.ts";
9
- import { handle } from "./result.ts";
10
-
11
- export function registerLaneTools(server: McpServer, deps: Deps): void {
12
- server.registerTool(
13
- "route",
14
- {
15
- description:
16
- "The rung for a lane (from Jev, else the lane file's Kind/Difficulty lines, else the profile default) or, without a lane file, a role's default rung, with the ladder above it. Rungs are backend:model#effort; a claude: rung comes with the agent to run it as. `provenance` says why: each threshold of the lane's bar with the value used against it, that value's confidence, source and date (`inferred: true` when a treat-like or a stand-in lent it), the rung's speed and cost facts, and catherd's own run evidence for it (shown, never used to route). A lane whose Kind: or Difficulty: the catalog does not know is refused with E_LANE_INVALID.",
17
- inputSchema: {
18
- run: z.string(),
19
- lane_file: z.string().optional(),
20
- role: z.enum(ROLES).default("worker"),
21
- },
22
- },
23
- (a) => handle(() => route(deps, { run: a.run, laneFile: a.lane_file, role: a.role })),
24
- );
25
-
26
- server.registerTool(
27
- "preflight",
28
- {
29
- description:
30
- "Run each lane's fast check once, on the base tree, behind the heavy-command lock, with a 120 s timeout. Each lane is pass, fails-as-expected, skipped (it checks a file the lane creates) or cannot-start; only cannot-start blocks. When the profile asks for confirmation, the first call returns the commands to show the user; call again with confirmed: true. Refused with E_LANE_INVALID, running nothing, while any lane's Kind: or Difficulty: is not one the catalog knows.",
31
- inputSchema: { run: z.string(), confirmed: z.boolean().optional() },
32
- },
33
- (a) => handle(() => preflight(deps, { run: a.run, confirmed: a.confirmed })),
34
- );
35
-
36
- server.registerTool(
37
- "climb",
38
- {
39
- description:
40
- "Move a routed lane one rung up its ladder and record why. Returns the next rung, or top: true, and any hints. Dispatch the lane again at that rung on a fresh thread. Pass env: true when the environment caused it (a missing service, a broken tool, a usage limit), not the rung. Refused with E_CLIMB_DESIGN when the evidence is a design question (Jev's finding answer is design, or a blocked climb's evidence is about lane ownership): send it to the architect instead.",
41
- inputSchema: {
42
- run: z.string(),
43
- lane: z.string().regex(ID_PATTERN),
44
- reason: z.enum(CLIMB_REASONS),
45
- evidence: z.string().optional(),
46
- env: z.boolean().optional(),
47
- },
48
- },
49
- (a) => handle(() => climb(deps, a)),
50
- );
51
-
52
- server.registerTool(
53
- "ask",
54
- {
55
- description:
56
- "Ask Jev one fixed question. finding: state { lane_file, finding } → design | code | unclear. same-defect: state { before, after } → yes | no. Falls back to the default when Jev is unsure or absent. Keep the state short, in English, and free of secrets.",
57
- inputSchema: {
58
- run: z.string(),
59
- question: z.enum(["finding", "same-defect"]),
60
- state: z.record(z.string(), z.string()),
61
- },
62
- },
63
- (a) => handle(() => ask(deps, a)),
64
- );
65
-
66
- server.registerTool(
67
- "land",
68
- {
69
- description:
70
- "Record a landed milestone after you commit it: its five-column ledger row (with the minutes it took) and state.md's last check and next step, with any hints. learned, when given, goes to this repo's knowledge.md. Returns digest: the milestone's digest (R/digests/<milestone>.md: A-lines, commit, lanes with rungs and climbs, reviewer findings, the verifier's verdict with carried items, minutes and tokens), for the milestone push to link. Refused with E_LAND_GATE unless, since the milestone's lanes started, a reviewer named reviewer-<milestone> ended ok (a dispatch, or a Claude subagent recorded with record_agent_run, role reviewer) and a verifier verdict naming the milestone was recorded ok (record_agent_run, role verifier; a headless verifier's reply opening VERDICT: PASS). A skip over an empty commit range is refused: commit first. skip: docs-only lands a milestone whose commit range changed only docs; skip: no-code one that changed no source file (put the evidence, e.g. a green pipeline, in evidence).",
71
- inputSchema: {
72
- run: z.string(),
73
- milestone: z.string().regex(ID_PATTERN),
74
- what: z.string().min(1),
75
- commit: z.string().regex(/^[0-9a-f]{7,40}$/),
76
- evidence: z.string().min(1),
77
- next: z.string().min(1),
78
- learned: z.string().min(1).optional(),
79
- skip: z.enum(LAND_SKIPS).optional(),
80
- },
81
- },
82
- (a) => handle(() => land(deps, a)),
83
- );
84
- }
@@ -1,57 +0,0 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import { ID_PATTERN } from "../../domain/ids.ts";
4
- import { gateCheck, gatePass } from "../../services/gate-service.ts";
5
- import { answer, park } from "../../services/questions.ts";
6
- import type { Deps } from "../../services/ports.ts";
7
- import { handle } from "./result.ts";
8
-
9
- // Spec 1.1 §7 and §8: the tools the enforced protocol adds.
10
- export function registerProtocolTools(server: McpServer, deps: Deps): void {
11
- const gate = {
12
- run: z.string(),
13
- item: z.string().min(1),
14
- command: z.string().min(1),
15
- paths: z.array(z.string().min(1)).min(1),
16
- };
17
-
18
- server.registerTool(
19
- "gate_check",
20
- {
21
- description:
22
- "The verifier, before running a gate item: { carried: true, passedAt, commit } when this repo already has a pass of the same command on the same content of paths (repo-relative; . for the whole repo: HEAD plus uncommitted not-ignored changes; a git-ignored input such as .env or a build output is hashed only when named as a path), so it reports the item as carried over from that commit instead of running it; else { carried: false }, and it runs the item. Either way the call is recorded as the verifier's current step, which status and peek show. Pass milestone (the M the verifier checks) so the milestone's digest lists only its own carried items.",
23
- inputSchema: { ...gate, milestone: z.string().regex(ID_PATTERN).optional() },
24
- },
25
- (a) => handle(() => gateCheck(deps, a)),
26
- );
27
-
28
- server.registerTool(
29
- "gate_pass",
30
- {
31
- description:
32
- "The verifier, after a gate item passed: records the command, the content hash of paths and the commit, with the evidence (a log path or the decisive line), so a later gate_check on unchanged content carries it over.",
33
- inputSchema: { ...gate, evidence: z.string().min(1) },
34
- },
35
- (a) => handle(() => gatePass(deps, a)),
36
- );
37
-
38
- server.registerTool(
39
- "park",
40
- {
41
- description:
42
- "An owner question only the user can answer: parks the milestone (land refuses it until answered) and puts it in front of state.md's next step, instead of stopping the run. Returns the parked milestones and hints: push the full question to the user with PushNotification, and go on with the milestones and runs that do not depend on it.",
43
- inputSchema: { run: z.string(), milestone: z.string().regex(ID_PATTERN), question: z.string().min(1) },
44
- },
45
- (a) => handle(() => park(deps, a)),
46
- );
47
-
48
- server.registerTool(
49
- "answer",
50
- {
51
- description:
52
- "The owner answered a parked milestone's question in this session: records the answer and unparks the milestone. status, peek and run_start list the questions still open.",
53
- inputSchema: { run: z.string(), milestone: z.string().regex(ID_PATTERN), answer: z.string().min(1) },
54
- },
55
- (a) => handle(() => answer(deps, a)),
56
- );
57
- }
@@ -1,148 +0,0 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import { ID_PATTERN } from "../../domain/ids.ts";
4
- import { ROLES } from "../../domain/roles.ts";
5
- import { knownQueueCapability } from "../../infra/codex-queue.ts";
6
- import { redact } from "../../infra/log.ts";
7
- import { registerSavedSecrets } from "../../services/jev-service.ts";
8
- import type { Deps } from "../../services/ports.ts";
9
- import {
10
- readKnowledge,
11
- readRunFile,
12
- recordAgentRun,
13
- result,
14
- setNext,
15
- startRun,
16
- writeRunFile,
17
- } from "../../services/run-service.ts";
18
- import { runsSummary, status } from "../../services/summary.ts";
19
- import { handle } from "./result.ts";
20
-
21
- export function registerRunTools(server: McpServer, deps: Deps): void {
22
- server.registerTool(
23
- "run_start",
24
- {
25
- description:
26
- "Start a catherd run for a git repository: creates its run folder (outside the repo) and state.md. Returns the run id, the folder, protocol (the next step of the milestone loop and its six-line checklist), and hints when state.md could not be written yet.",
27
- inputSchema: {
28
- repo: z.string().min(1),
29
- title: z.string().min(1),
30
- a_lines: z.array(z.string().min(1)).min(1),
31
- },
32
- },
33
- (a) => handle(() => startRun(deps, { repo: a.repo, title: a.title, aLines: a.a_lines })),
34
- );
35
-
36
- server.registerTool(
37
- "write_run_file",
38
- {
39
- description:
40
- "Write a file in the run folder: plan.md, lanes/Mx.Ly.md, a dossier or notes. The path is relative to the run folder; catherd's own files are refused.",
41
- inputSchema: { run: z.string(), path: z.string().min(1), content: z.string() },
42
- },
43
- (a) => handle(() => writeRunFile(a)),
44
- );
45
-
46
- server.registerTool(
47
- "read_run_file",
48
- {
49
- description: "Read a file in the run folder, by a path relative to it.",
50
- inputSchema: { run: z.string(), path: z.string().min(1) },
51
- },
52
- (a) => handle(() => readRunFile(a)),
53
- );
54
-
55
- server.registerTool(
56
- "status",
57
- {
58
- description:
59
- "catherd's version, and one screen per run: the state.md tail, live roles, totals, reported Claude subagents, Jev fallbacks, per backend how many dispatches ran native and isolated, budget and landed milestones. Without a run: every run with live roles, else the newest. Reads only.",
60
- inputSchema: { run: z.string().optional() },
61
- },
62
- (a) =>
63
- handle(async () => {
64
- registerSavedSecrets();
65
- const queue =
66
- deps.host.host === "codex" && !deps.host.conflict ? knownQueueCapability(process.env) : null;
67
- return redact(status(deps, a.run, queue));
68
- }),
69
- );
70
-
71
- server.registerTool(
72
- "result",
73
- {
74
- description:
75
- "A role's latest dispatch: its state, its record once finished, its capped reply, and hints (climb, violation, failed, limit and failover lines). Reading a finished record marks it read: catherd's messages announce records, and this is how you read one.",
76
- inputSchema: { run: z.string(), name: z.string().regex(ID_PATTERN) },
77
- },
78
- (a) => handle(() => result(deps, a)),
79
- );
80
-
81
- server.registerTool(
82
- "set_next",
83
- {
84
- description:
85
- "Record the run's next step, the last line of state.md: when pausing, or when the plan changes. dispatch, climb and land write state.md themselves. Returns { state } (state.md's text), and hints ['state.md not refreshed: …'] with state null when git fails (the step is still recorded).",
86
- inputSchema: { run: z.string(), next: z.string().min(1) },
87
- },
88
- (a) => handle(() => setNext(a)),
89
- );
90
-
91
- server.registerTool(
92
- "record_agent_run",
93
- {
94
- description:
95
- "After every native Claude subagent (Agent tool) of a run, record what the Agent result reported: total_tokens and duration_ms. The budget counts it. Pass lane (e.g. M1.L1) when the subagent worked a lane, so its time counts toward that lane's kind in the catalog timings. status says how the subagent's work ended: for a verifier it is the verdict, so pass status: \"failed\" when its verdict is FAIL; only a PASS is recorded ok.",
96
- inputSchema: {
97
- run: z.string(),
98
- name: z.string().regex(ID_PATTERN),
99
- role: z.enum(ROLES),
100
- rung: z.string().min(3),
101
- total_tokens: z.number().int().nonnegative(),
102
- duration_ms: z.number().nonnegative().optional(),
103
- cost_usd: z.number().nonnegative().optional(),
104
- status: z.enum(["ok", "failed", "cancelled"]).default("ok"),
105
- lane: z.string().regex(ID_PATTERN).optional(),
106
- },
107
- },
108
- (a) =>
109
- handle(() =>
110
- recordAgentRun(deps, {
111
- lane: a.lane,
112
- run: a.run,
113
- name: a.name,
114
- role: a.role,
115
- rung: a.rung,
116
- totalTokens: a.total_tokens,
117
- durationMs: a.duration_ms,
118
- costUsd: a.cost_usd,
119
- status: a.status,
120
- }),
121
- ),
122
- );
123
-
124
- server.registerTool(
125
- "read_knowledge",
126
- {
127
- description:
128
- "What past runs of this repo learned, one line per landed milestone that said something. Read it before an architect plans a new run.",
129
- inputSchema: { repo: z.string().min(1) },
130
- },
131
- (a) => handle(() => readKnowledge(a.repo)),
132
- );
133
-
134
- server.registerTool(
135
- "runs_summary",
136
- {
137
- description:
138
- "Per role and rung over past runs: runs, ok, refusals, climbs from that rung, seconds and tokens; reported Claude subagent runs; and what the user's harness customizations cost per run (median first-turn input, native against isolated; with a run, over that run's repo; null below 3 runs a side or when native is not dearer; none for Codex, which reports no per-request input). Reads only.",
139
- inputSchema: {
140
- run: z.string().optional(),
141
- repo: z.string().optional(),
142
- role: z.enum(ROLES).optional(),
143
- since_days: z.number().positive().optional(),
144
- },
145
- },
146
- (a) => handle(() => runsSummary({ run: a.run, repo: a.repo, role: a.role, sinceDays: a.since_days })),
147
- );
148
- }