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
@@ -0,0 +1,217 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { CatherdError } from "../domain/errors.ts";
4
+ import type { ProfilePatch } from "../domain/profile.ts";
5
+ import { isApproval } from "../domain/plan-doc.ts";
6
+ import { ROLES } from "../domain/roles.ts";
7
+ import { RUN_UNIT } from "../domain/run-events.ts";
8
+ import { liveDispatches } from "../domain/run-state.ts";
9
+ import { fetchBase, git, gitToplevel } from "../infra/git.ts";
10
+ import { writeTextAtomic } from "../infra/store.ts";
11
+ import { ensureDaemon } from "./daemon.ts";
12
+ import type { Deps } from "./ports.ts";
13
+ import { copyPlanInputs, planInputPaths, resolvePlanInputs } from "./plan-input.ts";
14
+ import { answer, openQuestions } from "./questions.ts";
15
+ import { appendEvent, isDaemonRun, readEvents, replayRun, withEventsLock } from "./run-events-store.ts";
16
+ import { runProfile } from "./run-pin.ts";
17
+ import { findRun, type Run } from "./run-store.ts";
18
+ import { result, startRun } from "./run-service.ts";
19
+
20
+ /** What the session's tools, the panel and the CLI share: they only append events and read the run's state. */
21
+
22
+ const readText = (file: string): string => {
23
+ try {
24
+ return readFileSync(file, "utf8");
25
+ } catch {
26
+ return "";
27
+ }
28
+ };
29
+
30
+ /** A 1.5 run has no daemon: this client neither answers nor cancels it. */
31
+ function daemonRun(id: string): Run {
32
+ const run = findRun(id);
33
+ if (!isDaemonRun(run))
34
+ throw new CatherdError("E_RUN_NOT_LIVE", `run ${id} is a 1.5 run: catherd 2.0 does not drive it`, {
35
+ fix: "finish it with catherd 1.x",
36
+ });
37
+ return run;
38
+ }
39
+
40
+ /** Fetches `origin/<base>` and refuses a base origin does not have (E_INPUT_INVALID); works from the last fetch offline. */
41
+ async function assertBase(repo: string, base: string): Promise<void> {
42
+ await fetchBase(repo, base);
43
+ const r = await git(repo, ["rev-parse", "--verify", "--quiet", `origin/${base}^{commit}`]);
44
+ if (r.kind !== "ok")
45
+ throw new CatherdError("E_INPUT_INVALID", `origin/${base} does not exist in ${repo}`, {
46
+ fix: "pass a branch origin has, or omit base",
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Starts a 2.0 run: its folder, its pin, `run.started`, and its daemon. The owner's plan files (`plan`, and any
52
+ * `plan:` A-line) are checked before the run exists, copied into it, and approved as input.
53
+ */
54
+ export async function runStart(
55
+ deps: Deps,
56
+ i: { repo: string; title: string; aLines: string[]; plan?: string[]; base?: string },
57
+ ): Promise<{
58
+ run: string;
59
+ dir: string;
60
+ daemon: "revived" | "alive";
61
+ notifyUrl: string;
62
+ plan: string[];
63
+ next: string;
64
+ }> {
65
+ const top = await gitToplevel(i.repo);
66
+ // a repo outside git is startRun's refusal
67
+ const plan = top ? resolvePlanInputs(top, [...(i.plan ?? []), ...planInputPaths(i.aLines)]) : [];
68
+ // a base run's branch must exist on origin before any run folder does
69
+ if (i.base && top) await assertBase(top, i.base);
70
+ const started = await startRun(deps, { repo: i.repo, title: i.title, aLines: i.aLines, base: i.base });
71
+ const run = findRun(started.run);
72
+ copyPlanInputs(run, plan);
73
+ await appendEvent(
74
+ run,
75
+ {
76
+ type: "run.started",
77
+ repo: run.meta.repo,
78
+ title: i.title,
79
+ aLines: i.aLines,
80
+ plan,
81
+ base: i.base ?? null,
82
+ engine: "2.0",
83
+ rolesOff: ROLES.filter((r) => !runProfile(deps, run).roles[r]?.enabled),
84
+ },
85
+ new Date(deps.now()),
86
+ );
87
+ if (plan.length) await appendEvent(run, { type: "plan.approved", by: "input" }, new Date(deps.now()));
88
+ const notifyUrl = deps.profiles.notifyUrl();
89
+ const daemon = ensureDaemon(run) === "alive" ? "alive" : "revived";
90
+ return {
91
+ run: run.id,
92
+ dir: run.dir,
93
+ daemon,
94
+ notifyUrl,
95
+ plan,
96
+ next: plan.length
97
+ ? `the run implements ${plan.length} plan file${plan.length === 1 ? "" : "s"} as written: status(run) shows it; parked questions reach ${notifyUrl} and this session`
98
+ : `the architect writes a plan first, and the run waits for your approval (answer approve): status(run) shows it; parked questions reach ${notifyUrl} and this session`,
99
+ };
100
+ }
101
+
102
+ /** Answers a parked unit: the owner's words reach the daemon as `question.answered`. */
103
+ export async function answerUnit(
104
+ deps: Deps,
105
+ i: { run: string; unit: string; answer: string },
106
+ ): Promise<{ unit: string; parked: string[] }> {
107
+ const run = daemonRun(i.run);
108
+ // the check and the append are one step: two answers to one question never both get through
109
+ await withEventsLock(run, async (add) => {
110
+ const state = replayRun(run);
111
+ const open = Object.keys(state.questions);
112
+ if (!open.includes(i.unit))
113
+ throw new CatherdError("E_INPUT_INVALID", `${i.unit} has no open question`, {
114
+ fix: open.length
115
+ ? `the open ones: ${open.join(", ")}`
116
+ : "status(run) shows the run's state: nothing waits for an answer now",
117
+ });
118
+ // the owner answers when they have fixed the plan: the roles read the copies, so copy again
119
+ if (state.plan.length) copyPlanInputs(run, state.plan);
120
+ // a bare approving word approves the plan (the first approval or a re-approval): reduce closes the question
121
+ if (i.unit === RUN_UNIT && state.questions[RUN_UNIT]?.reason === "approval" && isApproval(i.answer)) {
122
+ writeTextAtomic(join(run.dir, "plan-approved.md"), readText(join(run.dir, "plan.md")));
123
+ add({ type: "plan.approved", by: "owner" }, new Date(deps.now()));
124
+ return;
125
+ }
126
+ // a milestone's park is in questions.jsonl too, which land's gate reads
127
+ if (openQuestions(run).some((q) => q.milestone === i.unit))
128
+ await answer(deps, { run: run.id, milestone: i.unit, answer: i.answer });
129
+ add({ type: "question.answered", unit: i.unit, answer: i.answer, by: "owner" }, new Date(deps.now()));
130
+ });
131
+ ensureDaemon(run);
132
+ return { unit: i.unit, parked: Object.keys(replayRun(run).questions) };
133
+ }
134
+
135
+ /** Cancels the run; asking twice appends nothing. The daemon stops every live role, then ends the run. */
136
+ export async function cancelRun(
137
+ deps: Deps,
138
+ i: { run: string },
139
+ ): Promise<{ cancelling: true; live: string[] }> {
140
+ const run = daemonRun(i.run);
141
+ const s = replayRun(run);
142
+ const live = liveDispatches(s).map((d) => d.name);
143
+ if (!s.cancelling && s.phase !== "ended") {
144
+ await appendEvent(run, { type: "cancel.requested", by: "owner" }, new Date(deps.now()));
145
+ ensureDaemon(run);
146
+ }
147
+ return { cancelling: true, live };
148
+ }
149
+
150
+ export interface RunResult {
151
+ phase: ReturnType<typeof replayRun>["phase"];
152
+ outcome: ReturnType<typeof replayRun>["outcome"];
153
+ reportPath: string | null;
154
+ report: string | null;
155
+ milestones: {
156
+ id: string;
157
+ status: string;
158
+ commit: string | null;
159
+ digestPath: string | null;
160
+ knownIssues: string[];
161
+ }[];
162
+ /** with a name: the role's record, as 1.5's `result(run, name)` gives it */
163
+ role?: unknown;
164
+ }
165
+
166
+ /** The run's outcome and report; with a role's name, that role's record too. */
167
+ export async function runResult(deps: Deps, i: { run: string; name?: string }): Promise<RunResult> {
168
+ const run = daemonRun(i.run);
169
+ const s = replayRun(run);
170
+ const digests = new Map(
171
+ readEvents(run).flatMap((e) =>
172
+ e.type === "milestone.landed" ? [[e.milestone, e.digestPath] as const] : [],
173
+ ),
174
+ );
175
+ const file = join(run.dir, "report.md");
176
+ const has = existsSync(file);
177
+ return {
178
+ phase: s.phase,
179
+ outcome: s.outcome,
180
+ reportPath: has ? file : null,
181
+ report: has ? readFileSync(file, "utf8") : null,
182
+ milestones: s.milestones.map((m) => ({
183
+ id: m.id,
184
+ status: m.status,
185
+ commit: m.commit,
186
+ digestPath: digests.get(m.id) ?? null,
187
+ knownIssues: m.knownIssues,
188
+ })),
189
+ ...(i.name === undefined ? {} : { role: await result(deps, { run: run.id, name: i.name }) }),
190
+ };
191
+ }
192
+
193
+ /** The one `profile` tool: get, validate or set a profile, and the phone's topic url. */
194
+ export function profileTool(
195
+ deps: Deps,
196
+ i: {
197
+ action: "get" | "validate" | "set";
198
+ name?: string;
199
+ repo?: string;
200
+ patch?: ProfilePatch;
201
+ notifyUrl?: string;
202
+ },
203
+ ): unknown {
204
+ const repo = i.repo ?? null;
205
+ if (i.notifyUrl !== undefined) {
206
+ deps.profiles.setNotifyUrl(i.notifyUrl);
207
+ if (i.action === "set" && i.patch === undefined) return { notifyUrl: deps.profiles.notifyUrl() };
208
+ }
209
+ switch (i.action) {
210
+ case "get":
211
+ return deps.profiles.get(i.name, repo);
212
+ case "validate":
213
+ return deps.profiles.validate(i.name, repo);
214
+ case "set":
215
+ return deps.profiles.set(i.name, i.patch ?? {}, repo);
216
+ }
217
+ }