@oisincoveney/pipeline 3.24.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +14 -2
  2. package/defaults/pipeline.yaml +23 -5
  3. package/defaults/profiles.yaml +104 -21
  4. package/dist/argo-graph.js +90 -88
  5. package/dist/argo-submit.d.ts +110 -138
  6. package/dist/argo-submit.js +363 -378
  7. package/dist/argo-workflow.d.ts +156 -177
  8. package/dist/argo-workflow.js +96 -138
  9. package/dist/backlog.js +5 -4
  10. package/dist/bench/eval-report.js +21 -12
  11. package/dist/claude-settings-config.js +23 -31
  12. package/dist/claude-user-config.js +6 -16
  13. package/dist/cli/bootstrap-commands.js +3 -3
  14. package/dist/cli/doctor.d.ts +1 -1
  15. package/dist/cli/doctor.js +176 -171
  16. package/dist/cli/factory-commands.js +13 -13
  17. package/dist/cli/format.js +92 -86
  18. package/dist/cli/loop-commands.js +21 -21
  19. package/dist/cli/mcp-gateway-commands.js +12 -12
  20. package/dist/cli/plan-commands.js +85 -116
  21. package/dist/cli/program.d.ts +3 -3
  22. package/dist/cli/program.js +41 -43
  23. package/dist/cli/run-command.d.ts +1 -1
  24. package/dist/cli/run-command.js +25 -25
  25. package/dist/cli/run-commands.js +31 -29
  26. package/dist/cli/run-resolver.d.ts +1 -1
  27. package/dist/cli/run-resolver.js +30 -32
  28. package/dist/cli/run-service.d.ts +2 -3
  29. package/dist/cli/run-service.js +229 -239
  30. package/dist/cli/submit-options.js +85 -83
  31. package/dist/cluster-doctor.js +147 -175
  32. package/dist/codex-config.js +24 -30
  33. package/dist/commands/bench-command.js +9 -7
  34. package/dist/commands/coordinator-command.js +35 -0
  35. package/dist/commands/pipeline-command.js +13 -7
  36. package/dist/commands/runner-command-command.js +39 -24
  37. package/dist/commands/runner-node-command.js +31 -0
  38. package/dist/commands/ticket/complete.js +29 -33
  39. package/dist/commands/ticket/create.js +84 -82
  40. package/dist/commands/ticket/graph-check.js +11 -11
  41. package/dist/commands/ticket/next.js +36 -36
  42. package/dist/commands/ticket/sequence.js +11 -12
  43. package/dist/commands/ticket/shared.d.ts +5 -2
  44. package/dist/commands/ticket/shared.js +63 -66
  45. package/dist/commands/ticket/start.js +58 -67
  46. package/dist/commands/ticket-command.js +2 -2
  47. package/dist/config/defaults.d.ts +139 -140
  48. package/dist/config/defaults.js +81 -80
  49. package/dist/config/lint.js +52 -64
  50. package/dist/config/load.d.ts +4 -5
  51. package/dist/config/load.js +64 -74
  52. package/dist/config/schema/catalog.d.ts +2 -1
  53. package/dist/config/schema/catalog.js +3 -2
  54. package/dist/config/schema/mcp.js +46 -52
  55. package/dist/config/schemas.d.ts +382 -528
  56. package/dist/config/schemas.js +400 -398
  57. package/dist/config/validate.d.ts +1 -2
  58. package/dist/config/validate.js +236 -201
  59. package/dist/config.d.ts +2 -2
  60. package/dist/context/repo-map.js +105 -119
  61. package/dist/coordinator/composition.js +169 -0
  62. package/dist/coordinator/config.js +71 -0
  63. package/dist/coordinator/run-owner.js +64 -0
  64. package/dist/coordinator/wave-advance.js +148 -0
  65. package/dist/coordinator/wave-scheduler.js +50 -0
  66. package/dist/credentials/broker.d.ts +6 -8
  67. package/dist/credentials/broker.js +17 -16
  68. package/dist/credentials/codex-config.js +15 -19
  69. package/dist/credentials/file-targets.js +9 -15
  70. package/dist/credentials/local-codex-auth-sync.js +55 -58
  71. package/dist/credentials/opencode-config.js +28 -32
  72. package/dist/credentials/runner.js +13 -13
  73. package/dist/effect-sync-errors.js +28 -0
  74. package/dist/factory/create-experiment.js +285 -121
  75. package/dist/factory/exec.js +10 -12
  76. package/dist/factory/git-credentials.js +4 -4
  77. package/dist/factory/stamp-answers.js +10 -11
  78. package/dist/factory/template-update.js +65 -44
  79. package/dist/factory-lane.d.ts +26 -40
  80. package/dist/factory-lane.js +26 -25
  81. package/dist/gates.js +3 -3
  82. package/dist/git-remote-url.js +17 -19
  83. package/dist/hooks.d.ts +26 -26
  84. package/dist/hooks.js +20 -22
  85. package/dist/index.d.ts +4 -3
  86. package/dist/index.js +27 -36
  87. package/dist/install-commands/claude-code.js +100 -92
  88. package/dist/install-commands/host-selection.js +3 -6
  89. package/dist/install-commands/opencode.js +264 -259
  90. package/dist/install-commands/planner.js +163 -181
  91. package/dist/install-commands/result-format.js +1 -3
  92. package/dist/install-commands/shared.js +25 -42
  93. package/dist/install-commands/writer.js +22 -25
  94. package/dist/install-commands.js +8 -8
  95. package/dist/jscpd-output.js +29 -0
  96. package/dist/json-config-merge.js +14 -20
  97. package/dist/json-line-values.js +17 -6
  98. package/dist/loop/argo-poll.d.ts +0 -1
  99. package/dist/loop/argo-poll.js +15 -95
  100. package/dist/loop/backlog-records.js +1 -3
  101. package/dist/loop/controller-deps.js +150 -161
  102. package/dist/loop/controller.js +164 -186
  103. package/dist/loop/gh-checks.js +62 -61
  104. package/dist/loop/gh-runner.js +22 -25
  105. package/dist/loop/loop-command.js +66 -58
  106. package/dist/loop/loop-controller-entrypoint.js +29 -30
  107. package/dist/loop/merge.js +36 -40
  108. package/dist/loop/run-terminal-poll.js +69 -0
  109. package/dist/mcp/gateway-config.js +25 -29
  110. package/dist/mcp/gateway-doctor.js +77 -119
  111. package/dist/mcp/gateway-reconcile.js +17 -22
  112. package/dist/mcp/gateway-runtime.js +1 -3
  113. package/dist/mcp/host-config.js +37 -40
  114. package/dist/mcp/host-renderers.js +10 -18
  115. package/dist/mcp/repo-local-backends.js +51 -54
  116. package/dist/mcp/toolhive-vmcp.js +39 -39
  117. package/dist/model-resolver.js +29 -33
  118. package/dist/moka-global-config.d.ts +78 -55
  119. package/dist/moka-global-config.js +93 -92
  120. package/dist/moka-submit.d.ts +2179 -866
  121. package/dist/moka-submit.js +97 -99
  122. package/dist/opencode-project-config.js +34 -40
  123. package/dist/package-assets.js +1 -3
  124. package/dist/path-refs.js +2 -2
  125. package/dist/pipeline-init.js +25 -25
  126. package/dist/pipeline-runtime.d.ts +15 -14
  127. package/dist/pipeline-runtime.js +51 -50
  128. package/dist/planned-node.js +6 -3
  129. package/dist/planning/compile.d.ts +5 -5
  130. package/dist/planning/compile.js +140 -139
  131. package/dist/planning/dependency-refs.js +5 -5
  132. package/dist/planning/generate.d.ts +1296 -1296
  133. package/dist/planning/generate.js +307 -314
  134. package/dist/planning/graph.d.ts +0 -1
  135. package/dist/planning/graph.js +131 -149
  136. package/dist/remote/argo/model.d.ts +104 -122
  137. package/dist/remote/argo/model.js +205 -197
  138. package/dist/remote/argo/policy.js +39 -47
  139. package/dist/remote/argo/storage.js +81 -85
  140. package/dist/remote/jobs/execution-identity.js +70 -0
  141. package/dist/remote/jobs/job-outcome.js +108 -0
  142. package/dist/remote/jobs/manifest.js +96 -0
  143. package/dist/remote/jobs/model.d.ts +158 -0
  144. package/dist/remote/jobs/model.js +162 -0
  145. package/dist/remote/jobs/node-bootstrap.js +213 -0
  146. package/dist/remote/jobs/policy.js +123 -0
  147. package/dist/remote/jobs/reconcile.js +100 -0
  148. package/dist/remote/jobs/run-spec-client.js +64 -0
  149. package/dist/remote/jobs/run-spec-path.js +31 -0
  150. package/dist/remote/jobs/run-spec-server.d.ts +34 -0
  151. package/dist/remote/jobs/run-spec-server.js +116 -0
  152. package/dist/remote/jobs/submit.d.ts +23 -0
  153. package/dist/remote/jobs/submit.js +116 -0
  154. package/dist/remote/jobs/watch.js +59 -0
  155. package/dist/remote/submit/argo-submission.d.ts +3 -1
  156. package/dist/remote/submit/argo-submission.js +53 -58
  157. package/dist/remote/submit/compilation.d.ts +2 -0
  158. package/dist/remote/submit/compilation.js +46 -49
  159. package/dist/remote/submit/event-boundary.js +62 -76
  160. package/dist/remote/submit/hook-events.d.ts +6 -0
  161. package/dist/remote/submit/io.d.ts +7 -1
  162. package/dist/remote/submit/io.js +40 -43
  163. package/dist/remote/submit/jobs-submission.d.ts +59 -0
  164. package/dist/remote/submit/jobs-submission.js +97 -0
  165. package/dist/remote/submit/jobs-submit-result.d.ts +25 -0
  166. package/dist/remote/submit/jobs-submit-result.js +34 -0
  167. package/dist/remote/submit/options.d.ts +49 -0
  168. package/dist/remote/submit/options.js +27 -0
  169. package/dist/remote/submit/service.d.ts +40 -1
  170. package/dist/remote/submit/service.js +67 -49
  171. package/dist/run-control/command-context.js +6 -8
  172. package/dist/run-control/commands.js +10 -10
  173. package/dist/run-control/contracts.d.ts +52 -101
  174. package/dist/run-control/contracts.js +83 -69
  175. package/dist/run-control/detach.js +66 -72
  176. package/dist/run-control/execution-store.js +0 -0
  177. package/dist/run-control/file-errors.js +1 -3
  178. package/dist/run-control/logical-segment.js +3 -3
  179. package/dist/run-control/next-node.js +67 -78
  180. package/dist/run-control/postgres/postgres-run-control-store.js +196 -225
  181. package/dist/run-control/resume-command.js +34 -41
  182. package/dist/run-control/run-artifacts-command.js +84 -105
  183. package/dist/run-control/run-command-domain.js +7 -11
  184. package/dist/run-control/run-control-store.js +9 -9
  185. package/dist/run-control/run-query-command.js +73 -100
  186. package/dist/run-control/run-record.js +5 -3
  187. package/dist/run-control/run-state-lock.js +5 -5
  188. package/dist/run-control/runtime-event-projection.js +38 -50
  189. package/dist/run-control/runtime-reporter.js +86 -102
  190. package/dist/run-control/stop-command.js +42 -53
  191. package/dist/run-control/store-fs-effects.js +3 -1
  192. package/dist/run-control/store-manifest.js +38 -13
  193. package/dist/run-control/store-paths.js +3 -3
  194. package/dist/run-control/store.js +1 -1
  195. package/dist/run-control/submit-result.js +21 -21
  196. package/dist/run-control/supervisor.js +78 -68
  197. package/dist/run-control/workspace.js +2 -2
  198. package/dist/run-state/git-refs.js +258 -307
  199. package/dist/runner/opencode-excludes.js +4 -4
  200. package/dist/runner/subprocess-result.js +28 -37
  201. package/dist/runner/subprocess.js +41 -41
  202. package/dist/runner/timeouts.js +14 -13
  203. package/dist/runner-command/dynamic-command.js +46 -39
  204. package/dist/runner-command/environment-commands.js +91 -0
  205. package/dist/runner-command/finalize.js +174 -153
  206. package/dist/runner-command/lifecycle-context.js +115 -130
  207. package/dist/runner-command/lifecycle.js +78 -72
  208. package/dist/runner-command/pre-schedule.js +203 -223
  209. package/dist/runner-command/run.js +450 -448
  210. package/dist/runner-command/schedule-source-options.js +8 -10
  211. package/dist/runner-command/select-ready-wave.js +39 -30
  212. package/dist/runner-command/task-descriptor.js +11 -17
  213. package/dist/runner-command-contract.d.ts +147 -157
  214. package/dist/runner-command-contract.js +403 -411
  215. package/dist/runner-event-schema.d.ts +364 -505
  216. package/dist/runner-event-schema.js +187 -175
  217. package/dist/runner-event-sink.js +37 -43
  218. package/dist/runner-output.js +6 -6
  219. package/dist/runner.d.ts +57 -37
  220. package/dist/runner.js +176 -127
  221. package/dist/runtime/actor-ids.d.ts +1 -0
  222. package/dist/runtime/actor-ids.js +2 -2
  223. package/dist/runtime/agent-node/agent-node.js +99 -110
  224. package/dist/runtime/agent-node/handoff-finalization.js +23 -24
  225. package/dist/runtime/agent-node/model-selection.js +19 -22
  226. package/dist/runtime/agent-node/output-finalization.js +174 -204
  227. package/dist/runtime/agent-node/prompt-rendering.js +208 -251
  228. package/dist/runtime/agent-node/session-execution.js +43 -39
  229. package/dist/runtime/builtins/builtins.js +218 -284
  230. package/dist/runtime/changed-files/changed-files.js +29 -34
  231. package/dist/runtime/command-executor/command-executor.js +31 -23
  232. package/dist/runtime/config-error.js +1 -3
  233. package/dist/runtime/context/context.js +35 -37
  234. package/dist/runtime/contracts/contracts.d.ts +3 -0
  235. package/dist/runtime/contracts/index.d.ts +1 -1
  236. package/dist/runtime/detached-race.js +11 -14
  237. package/dist/runtime/drain-merge/drain-merge.js +139 -164
  238. package/dist/runtime/durable-store/acquisition.js +5 -3
  239. package/dist/runtime/durable-store/durable-store.d.ts +3 -1
  240. package/dist/runtime/durable-store/postgres/client.js +29 -0
  241. package/dist/runtime/durable-store/postgres/migrate-substrate.js +34 -29
  242. package/dist/runtime/durable-store/postgres/postgres-store.js +18 -20
  243. package/dist/runtime/events/events.js +273 -331
  244. package/dist/runtime/events/index.js +2 -2
  245. package/dist/runtime/execution-envelope.js +44 -0
  246. package/dist/runtime/gates/adjudication/llm-judge.js +21 -24
  247. package/dist/runtime/gates/adjudication/structured-claim.js +15 -16
  248. package/dist/runtime/gates/adjudicator/adjudicator.js +27 -27
  249. package/dist/runtime/gates/adjudicator/index.js +2 -2
  250. package/dist/runtime/gates/contract.js +5 -9
  251. package/dist/runtime/gates/gates.js +9 -8
  252. package/dist/runtime/gates/kinds/acceptance/acceptance.js +68 -71
  253. package/dist/runtime/gates/kinds/acceptance/index.js +2 -2
  254. package/dist/runtime/gates/kinds/artifact/artifact.js +4 -4
  255. package/dist/runtime/gates/kinds/artifact/index.js +2 -2
  256. package/dist/runtime/gates/kinds/builtin/builtin.js +4 -4
  257. package/dist/runtime/gates/kinds/builtin/index.js +2 -2
  258. package/dist/runtime/gates/kinds/changed-files/changed-files.js +31 -35
  259. package/dist/runtime/gates/kinds/changed-files/index.js +2 -2
  260. package/dist/runtime/gates/kinds/command/command.js +3 -3
  261. package/dist/runtime/gates/kinds/command/index.js +2 -2
  262. package/dist/runtime/gates/kinds/json-schema/index.js +2 -2
  263. package/dist/runtime/gates/kinds/json-schema/json-schema.js +7 -6
  264. package/dist/runtime/gates/kinds/verdict/index.js +2 -2
  265. package/dist/runtime/gates/kinds/verdict/verdict.js +3 -3
  266. package/dist/runtime/gates/orchestrator.js +72 -86
  267. package/dist/runtime/gates/registry.js +1 -3
  268. package/dist/runtime/handoff.d.ts +2 -1
  269. package/dist/runtime/handoff.js +43 -46
  270. package/dist/runtime/hooks/command-hook.js +57 -71
  271. package/dist/runtime/hooks/context.js +21 -25
  272. package/dist/runtime/hooks/events.js +84 -100
  273. package/dist/runtime/hooks/execution.js +6 -12
  274. package/dist/runtime/hooks/hooks.js +34 -23
  275. package/dist/runtime/hooks/invocation.js +23 -29
  276. package/dist/runtime/hooks/module-hook.js +36 -35
  277. package/dist/runtime/hooks/policy.js +32 -29
  278. package/dist/runtime/hooks/results.js +56 -66
  279. package/dist/runtime/journal-acquisition.d.ts +3 -2
  280. package/dist/runtime/journal-acquisition.js +14 -14
  281. package/dist/runtime/json-validation/json-validation.js +51 -72
  282. package/dist/runtime/launch-plan-args.js +39 -0
  283. package/dist/runtime/local-scheduler.js +46 -48
  284. package/dist/runtime/node-execution.js +376 -445
  285. package/dist/runtime/node-protocol/node-protocol.js +36 -79
  286. package/dist/runtime/node-state-store.d.ts +4 -1
  287. package/dist/runtime/node-state-store.js +13 -57
  288. package/dist/runtime/node-state-tracker.js +46 -54
  289. package/dist/runtime/open-pull-request/open-pull-request.js +201 -187
  290. package/dist/runtime/opencode-adapter.js +23 -24
  291. package/dist/runtime/opencode-agent-name.js +6 -6
  292. package/dist/runtime/opencode-runtime.js +115 -93
  293. package/dist/runtime/opencode-server.js +39 -53
  294. package/dist/runtime/opencode-session-executor.js +312 -344
  295. package/dist/runtime/parallel-node/parallel-node.js +123 -134
  296. package/dist/runtime/parallel-worktrees/parallel-worktrees.js +90 -73
  297. package/dist/runtime/protected-paths/protected-paths.js +70 -71
  298. package/dist/runtime/remediation/remediation.js +202 -231
  299. package/dist/runtime/retry.js +8 -10
  300. package/dist/runtime/run-journal.d.ts +2 -0
  301. package/dist/runtime/run-journal.js +7 -7
  302. package/dist/runtime/runtime-results.js +54 -69
  303. package/dist/runtime/scheduled-dependencies.d.ts +2 -1
  304. package/dist/runtime/scheduled-dependencies.js +61 -75
  305. package/dist/runtime/scheduler.js +179 -202
  306. package/dist/runtime/services/agent-node-runtime-service.js +3 -3
  307. package/dist/runtime/services/backlog-service.d.ts +1 -2
  308. package/dist/runtime/services/backlog-service.js +8 -14
  309. package/dist/runtime/services/command-executor-service.js +1 -1
  310. package/dist/runtime/services/config-io-service.js +24 -21
  311. package/dist/runtime/services/drain-merge-git-service.js +1 -1
  312. package/dist/runtime/services/file-system-service.js +9 -7
  313. package/dist/runtime/services/git-porcelain-service.js +6 -6
  314. package/dist/runtime/services/kubernetes-argo-service.d.ts +10 -3
  315. package/dist/runtime/services/kubernetes-argo-service.js +90 -84
  316. package/dist/runtime/services/kubernetes-jobs-read-service.js +115 -0
  317. package/dist/runtime/services/kubernetes-jobs-service.d.ts +37 -0
  318. package/dist/runtime/services/kubernetes-jobs-service.js +45 -0
  319. package/dist/runtime/services/kubernetes-jobs-watch-service.js +61 -0
  320. package/dist/runtime/services/kubernetes-lease-service.js +21 -0
  321. package/dist/runtime/services/mcp-gateway-service.js +143 -94
  322. package/dist/runtime/services/open-pull-request-git-service.js +1 -3
  323. package/dist/runtime/services/opencode-runtime-server-service.js +10 -12
  324. package/dist/runtime/services/opencode-sdk-service.js +7 -7
  325. package/dist/runtime/services/repo-io-service.js +10 -12
  326. package/dist/runtime/services/run-journal-file-service.js +2 -2
  327. package/dist/runtime/services/runner-command-io-service.js +48 -52
  328. package/dist/runtime/services/runner-event-sink-http-service.js +32 -39
  329. package/dist/runtime/step/step-node.js +15 -15
  330. package/dist/runtime/workflow-execution.js +56 -57
  331. package/dist/runtime/workflow-lifecycle.js +82 -101
  332. package/dist/runtime/yeet-event-record.js +104 -0
  333. package/dist/runtime/yeet-executor.js +184 -0
  334. package/dist/safe-json.js +16 -16
  335. package/dist/schedule/backlog-context.js +122 -116
  336. package/dist/schedule/baseline.js +249 -255
  337. package/dist/schedule/passes/coverage.js +63 -68
  338. package/dist/schedule/passes/drain-merge.js +34 -40
  339. package/dist/schedule/passes/ids.js +15 -17
  340. package/dist/schedule/passes/models.js +40 -37
  341. package/dist/schedule/passes/open-pull-request.js +16 -20
  342. package/dist/schedule/passes/references.js +18 -22
  343. package/dist/schedule/prompts.js +135 -141
  344. package/dist/schedule/scheduling-roles.js +8 -14
  345. package/dist/schema-boundary.d.ts +4 -0
  346. package/dist/schema-boundary.js +145 -0
  347. package/dist/serialized-write-queue.js +2 -2
  348. package/dist/standard-output-schemas.js +61 -59
  349. package/dist/strings.js +12 -12
  350. package/dist/task-ref.js +5 -5
  351. package/dist/tickets/apply-ticket-plan.js +52 -61
  352. package/dist/tickets/backlog-task-store.d.ts +15 -23
  353. package/dist/tickets/backlog-task-store.js +82 -94
  354. package/dist/tickets/completion/complete-ticket.js +53 -59
  355. package/dist/tickets/ticket-graph-dto.d.ts +24 -48
  356. package/dist/tickets/ticket-graph-dto.js +40 -40
  357. package/dist/tickets/ticket-graph.d.ts +7 -2
  358. package/dist/tickets/ticket-graph.js +36 -50
  359. package/dist/tickets/ticket-plan-command-args.js +6 -10
  360. package/dist/tickets/ticket-plan-render.js +10 -16
  361. package/dist/tickets/ticket-plan.d.ts +32 -41
  362. package/dist/tickets/ticket-plan.js +97 -77
  363. package/dist/tickets/ticket-selection.d.ts +3 -1
  364. package/dist/tickets/ticket-selection.js +37 -52
  365. package/dist/tickets/ticket-task-index.js +13 -17
  366. package/dist/tickets/validation-error-format.js +3 -10
  367. package/dist/token-estimator.js +12 -7
  368. package/dist/unknown-fields.js +21 -0
  369. package/dist/workflow-submit-contract.d.ts +9 -10
  370. package/dist/workflow-submit-contract.js +10 -9
  371. package/docs/adr-moka-postgres-schema-isolation.md +48 -0
  372. package/docs/adr-opencode-first-goal-loop-runtime.md +11 -11
  373. package/docs/config-architecture.md +13 -13
  374. package/docs/mcp-host-isolation.md +1 -0
  375. package/docs/moka-orchestrator-design.md +3 -3
  376. package/docs/operator-guide.md +1 -1
  377. package/docs/pipeline-console-runner-contract.md +10 -6
  378. package/docs/runtime-actor-model.md +18 -18
  379. package/docs/runtime-test-subsystems.md +200 -0
  380. package/docs/slash-command-adapter-contract.md +3 -3
  381. package/package.json +49 -85
  382. package/dist/config/schema/reference-validation.js +0 -83
@@ -1,12 +1,14 @@
1
+ import { booleanValue, mutableArray, numberValue, parseJsonWithSchema, requiredString, struct } from "../schema-boundary.js";
1
2
  //#region src/bench/eval-report.ts
2
- function buildEvalReport(results) {
3
- const variants = [...new Set(results.map((r) => r.variant))].sort();
4
- return {
5
- tasks: new Set(results.map((r) => r.task)).size,
6
- variants: variants.map((variant) => summarizeVariant(variant, results.filter((r) => r.variant === variant)))
7
- };
8
- }
9
- function summarizeVariant(variant, runs) {
3
+ const evalRunResultsSchema = mutableArray(struct({
4
+ costTokens: numberValue(),
5
+ resolved: booleanValue(),
6
+ task: requiredString,
7
+ variant: requiredString,
8
+ wallMs: numberValue()
9
+ }));
10
+ const parseEvalRunResultsJson = (source) => parseJsonWithSchema(evalRunResultsSchema, source);
11
+ const summarizeVariant = (variant, runs) => {
10
12
  const resolved = runs.filter((r) => r.resolved).length;
11
13
  const totalWall = runs.reduce((sum, r) => sum + r.wallMs, 0);
12
14
  return {
@@ -17,11 +19,18 @@ function summarizeVariant(variant, runs) {
17
19
  totalCostTokens: runs.reduce((sum, r) => sum + r.costTokens, 0),
18
20
  variant
19
21
  };
20
- }
21
- function renderEvalReport(report) {
22
+ };
23
+ const buildEvalReport = (results) => {
24
+ const variants = [...new Set(results.map((r) => r.variant))].toSorted();
25
+ return {
26
+ tasks: new Set(results.map((r) => r.task)).size,
27
+ variants: variants.map((variant) => summarizeVariant(variant, results.filter((r) => r.variant === variant)))
28
+ };
29
+ };
30
+ const renderEvalReport = (report) => {
22
31
  const lines = [`Eval over ${report.tasks} task(s):`, "variant | resolved | rate | tokens | avg ms"];
23
32
  for (const v of report.variants) lines.push(`${v.variant} | ${v.resolved}/${v.count} | ${(v.resolutionRate * 100).toFixed(0)}% | ${v.totalCostTokens} | ${v.avgWallMs}`);
24
33
  return lines.join("\n");
25
- }
34
+ };
26
35
  //#endregion
27
- export { buildEvalReport, renderEvalReport };
36
+ export { buildEvalReport, parseEvalRunResultsJson, renderEvalReport };
@@ -1,7 +1,27 @@
1
1
  import { applyJsonEdit, ensureTrailingNewline, formatJson, isRecord, parseJsonRecord, setIfMissing } from "./json-config-merge.js";
2
2
  //#region src/claude-settings-config.ts
3
- function mergeClaudeSettings(currentText, projection) {
4
- if (currentText === void 0) return {
3
+ const applyMcpServersProjection = (content, parsed, projection) => Object.entries(projection.mcpServers ?? {}).reduce((nextContent, [name, server]) => setIfMissing(nextContent, parsed, ["mcpServers", name], server), content);
4
+ const projectedAllowList = (projection) => projection.permissions?.allow ?? [];
5
+ const unionPreservingOrder = (existing, extra) => {
6
+ const merged = [...existing];
7
+ for (const entry of extra) if (!merged.includes(entry)) merged.push(entry);
8
+ return merged;
9
+ };
10
+ const sameOrderedList = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
11
+ const permissionAllowList = (parsed) => {
12
+ const { permissions } = parsed;
13
+ if (!isRecord(permissions)) return [];
14
+ const { allow } = permissions;
15
+ return Array.isArray(allow) ? allow.filter((entry) => typeof entry === "string") : [];
16
+ };
17
+ const applyPermissionsAllow = (content, parsed, projection) => {
18
+ const existing = permissionAllowList(parsed);
19
+ const merged = unionPreservingOrder(existing, projectedAllowList(projection));
20
+ return sameOrderedList(existing, merged) ? content : applyJsonEdit(content, ["permissions", "allow"], merged);
21
+ };
22
+ const applyClaudeProjection = (currentText, parsed, projection) => applyPermissionsAllow(applyMcpServersProjection(currentText, parsed, projection), parsed, projection);
23
+ const mergeClaudeSettings = (currentText = "", projection) => {
24
+ if (currentText === "") return {
5
25
  content: formatJson(projection),
6
26
  ok: true
7
27
  };
@@ -11,34 +31,6 @@ function mergeClaudeSettings(currentText, projection) {
11
31
  content: ensureTrailingNewline(applyClaudeProjection(currentText, parsed.value, projection)),
12
32
  ok: true
13
33
  };
14
- }
15
- function applyClaudeProjection(currentText, parsed, projection) {
16
- return applyPermissionsAllow(applyMcpServersProjection(currentText, parsed, projection), parsed, projection);
17
- }
18
- function applyMcpServersProjection(content, parsed, projection) {
19
- return Object.entries(projection.mcpServers ?? {}).reduce((nextContent, [name, server]) => setIfMissing(nextContent, parsed, ["mcpServers", name], server), content);
20
- }
21
- function applyPermissionsAllow(content, parsed, projection) {
22
- const existing = permissionAllowList(parsed);
23
- const merged = unionPreservingOrder(existing, projectedAllowList(projection));
24
- return sameOrderedList(existing, merged) ? content : applyJsonEdit(content, ["permissions", "allow"], merged);
25
- }
26
- function projectedAllowList(projection) {
27
- return projection.permissions?.allow ?? [];
28
- }
29
- function unionPreservingOrder(existing, extra) {
30
- const merged = [...existing];
31
- for (const entry of extra) if (!merged.includes(entry)) merged.push(entry);
32
- return merged;
33
- }
34
- function sameOrderedList(a, b) {
35
- return a.length === b.length && a.every((value, index) => value === b[index]);
36
- }
37
- function permissionAllowList(parsed) {
38
- const permissions = parsed.permissions;
39
- if (!isRecord(permissions)) return [];
40
- const allow = permissions.allow;
41
- return Array.isArray(allow) ? allow.filter((entry) => typeof entry === "string") : [];
42
- }
34
+ };
43
35
  //#endregion
44
36
  export { mergeClaudeSettings };
@@ -1,28 +1,18 @@
1
1
  import { applyJsonEdit, ensureTrailingNewline, formatJson, parseJsonRecord, setIfMissing } from "./json-config-merge.js";
2
2
  //#region src/claude-user-config.ts
3
- function mergeClaudeUserConfig(currentText, projection) {
4
- if (currentText === void 0) return {
3
+ const mergeClaudeUserConfigWith = (projection, project, currentText = "") => {
4
+ if (currentText === "") return {
5
5
  content: formatJson(projection),
6
6
  ok: true
7
7
  };
8
8
  const parsed = parseJsonRecord(currentText);
9
9
  if (!parsed.ok) return parsed;
10
10
  return {
11
- content: ensureTrailingNewline(Object.entries(projection.mcpServers ?? {}).reduce((nextContent, [name, server]) => setIfMissing(nextContent, parsed.value, ["mcpServers", name], server), currentText)),
11
+ content: ensureTrailingNewline(project(currentText, parsed.value, projection)),
12
12
  ok: true
13
13
  };
14
- }
15
- function replaceClaudeUserMcpServers(currentText, projection) {
16
- if (currentText === void 0) return {
17
- content: formatJson(projection),
18
- ok: true
19
- };
20
- const parsed = parseJsonRecord(currentText);
21
- if (!parsed.ok) return parsed;
22
- return {
23
- content: ensureTrailingNewline(applyJsonEdit(currentText, ["mcpServers"], projection.mcpServers ?? {})),
24
- ok: true
25
- };
26
- }
14
+ };
15
+ const mergeClaudeUserConfig = (projection, currentText = "") => mergeClaudeUserConfigWith(projection, (text, parsed, value) => Object.entries(value.mcpServers ?? {}).reduce((nextContent, [name, server]) => setIfMissing(nextContent, parsed, ["mcpServers", name], server), text), currentText);
16
+ const replaceClaudeUserMcpServers = (projection, currentText = "") => mergeClaudeUserConfigWith(projection, (text, _parsed, value) => applyJsonEdit(text, ["mcpServers"], value.mcpServers ?? {}), currentText);
27
17
  //#endregion
28
18
  export { mergeClaudeUserConfig, replaceClaudeUserMcpServers };
@@ -4,10 +4,10 @@ import { runDoctor } from "./doctor.js";
4
4
  import { formatDoctorResult } from "./format.js";
5
5
  import { resolve } from "node:path";
6
6
  //#region src/cli/bootstrap-commands.ts
7
- function registerBootstrapCommands(program) {
7
+ const registerBootstrapCommands = (program) => {
8
8
  program.command("doctor").description("Check local prerequisites for pipeline init and execution").option("--cluster [namespace]", "also check runner-job Kubernetes prerequisites").option("--json", "print machine-readable readiness results").option("--kube-context <context>", "kubectl context for cluster checks").option("--kubeconfig <path>", "kubeconfig path for cluster checks").action(async (flags) => {
9
9
  const result = await runDoctor(process.env.PIPELINE_TARGET_PATH ?? process.cwd(), flags);
10
- console.log(flags.json ? JSON.stringify(result) : formatDoctorResult(result));
10
+ console.log(flags.json === true ? JSON.stringify(result) : formatDoctorResult(result));
11
11
  if (!result.passed) throw new Error("Doctor checks failed.");
12
12
  });
13
13
  program.command("init").description([
@@ -34,6 +34,6 @@ function registerBootstrapCommands(program) {
34
34
  console.log(formatCodexAuthSyncResult(result));
35
35
  if (!result.ok) process.exitCode = 1;
36
36
  });
37
- }
37
+ };
38
38
  //#endregion
39
39
  export { registerBootstrapCommands };
@@ -16,6 +16,6 @@ interface DoctorFlags {
16
16
  kubeContext?: string;
17
17
  kubeconfig?: string;
18
18
  }
19
- declare function runDoctor(cwd: string, options?: DoctorFlags): Promise<DoctorResult>;
19
+ declare const runDoctor: (cwd: string, options?: DoctorFlags) => Promise<DoctorResult>;
20
20
  //#endregion
21
21
  export { DoctorCheck, DoctorFlags, DoctorResult, runDoctor };
@@ -2,6 +2,7 @@ import { PipelineConfigError } from "../config/schemas.js";
2
2
  import { loadPipelineConfig } from "../config/load.js";
3
3
  import "../config.js";
4
4
  import { loadMokaGlobalConfig } from "../moka-global-config.js";
5
+ import { isRecord } from "../safe-json.js";
5
6
  import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
6
7
  import { defaultClusterDoctorNamespace, runClusterDoctor } from "../cluster-doctor.js";
7
8
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
@@ -23,50 +24,123 @@ const OPENCODE_AGENT_LIST_ARGS = [
23
24
  "list",
24
25
  "--json"
25
26
  ];
26
- const BULLET_PREFIX_RE = /^[-*]\s+/;
27
- const LINE_RE = /\r?\n/;
28
- async function runDoctor(cwd, options = {}) {
29
- const commandChecks = await Promise.all([
30
- checkCommand("npx", ["--version"], cwd),
31
- checkCommand("opencode", ["--version"], cwd),
32
- checkCommand("fallow", ["--version"], cwd)
33
- ]);
34
- const configCheck = checkPipelineConfig(cwd);
35
- const config = configCheck.passed ? loadPipelineConfig(cwd) : null;
36
- const [sdkCheck, agentVisibility] = await Promise.all([checkOpenCodeSdk(), config ? checkMokaAgents(cwd, config) : Promise.resolve({ check: {
37
- detail: "skipped because pipeline config is invalid",
27
+ const BULLET_PREFIX_RE = /^[-*]\s+/u;
28
+ const LINE_RE = /\r?\n/u;
29
+ const clusterNamespace = (value, configuredNamespace) => typeof value === "string" && value.length > 0 ? value : configuredNamespace ?? defaultClusterDoctorNamespace();
30
+ const checkPipelineConfig = (cwd) => {
31
+ try {
32
+ loadPipelineConfig(cwd);
33
+ return {
34
+ detail: "valid",
35
+ name: "pipeline-config",
36
+ passed: true
37
+ };
38
+ } catch (error) {
39
+ let message = "invalid";
40
+ if (error instanceof PipelineConfigError) message = error.issues.map((issue) => issue.message).join("; ");
41
+ else if (error instanceof Error) message = error.message;
42
+ return {
43
+ detail: message || "missing or invalid",
44
+ name: "pipeline-config",
45
+ passed: false
46
+ };
47
+ }
48
+ };
49
+ const skippedAgentVisibility = (detail) => ({
50
+ check: {
51
+ detail: "skipped because OpenCode agent listing is unavailable",
38
52
  name: "moka-agents",
39
53
  passed: true
40
- } })]);
41
- const globalConfig = loadMokaGlobalConfig();
42
- const clusterResult = options.cluster ? await runClusterDoctor({
43
- kubeContext: options.kubeContext,
44
- kubeconfigPath: options.kubeconfig ?? globalConfig?.momokaya.kubernetes.kubeconfig,
45
- namespace: clusterNamespace(options.cluster, globalConfig?.momokaya.kubernetes.namespace)
46
- }) : { checks: [] };
47
- const checks = [
48
- ...commandChecks,
49
- configCheck,
50
- sdkCheck,
51
- agentVisibility.check,
52
- ...clusterResult.checks
53
- ];
54
- const warnings = [...agentVisibility.warning ? [agentVisibility.warning] : [], ...headlessPermissionWarnings(cwd)];
55
- const blockers = checks.filter((check) => !check.passed);
54
+ },
55
+ warning: {
56
+ detail,
57
+ name: "moka-agents",
58
+ passed: true
59
+ }
60
+ });
61
+ const expectedRunAgentNames = (config) => {
62
+ const profiles = /* @__PURE__ */ new Set();
63
+ for (const catalog of Object.values(config.scheduler.node_catalogs)) for (const node of Object.values(catalog.nodes)) if (RUN_READINESS_CATEGORIES.has(node.category)) profiles.add(node.profile);
64
+ return [...profiles].filter((profileId) => config.profiles[profileId]?.runner === "opencode").map(opencodeAgentName).toSorted((a, b) => a.localeCompare(b));
65
+ };
66
+ const collectAgentNames = (value, names, inAgentList) => {
67
+ if (typeof value === "string") {
68
+ if (inAgentList) names.add(value);
69
+ return inAgentList;
70
+ }
71
+ if (Array.isArray(value)) {
72
+ let recognized = inAgentList;
73
+ for (const item of value) recognized = collectAgentNames(item, names, inAgentList) || recognized;
74
+ return recognized;
75
+ }
76
+ if (!isRecord(value)) return false;
77
+ let recognized = false;
78
+ for (const key of [
79
+ "agent",
80
+ "id",
81
+ "name",
82
+ "subagent_type",
83
+ "title"
84
+ ]) if (typeof value[key] === "string") {
85
+ names.add(value[key]);
86
+ recognized = true;
87
+ }
88
+ for (const key of [
89
+ "agents",
90
+ "data",
91
+ "items",
92
+ "result"
93
+ ]) {
94
+ const item = value[key];
95
+ if (Array.isArray(item) || typeof item === "object" && item !== null) recognized = collectAgentNames(item, names, true) || recognized;
96
+ }
97
+ return recognized;
98
+ };
99
+ const visibleAgentNames = (stdout) => {
100
+ const names = /* @__PURE__ */ new Set();
101
+ try {
102
+ const parsed = JSON.parse(stdout);
103
+ return {
104
+ ambiguous: false,
105
+ names,
106
+ recognized: collectAgentNames(parsed, names, Array.isArray(parsed))
107
+ };
108
+ } catch {
109
+ for (const line of stdout.split(LINE_RE)) {
110
+ const name = line.trim().replace(BULLET_PREFIX_RE, "");
111
+ if (name !== "") names.add(name);
112
+ }
113
+ }
56
114
  return {
57
- blockers,
58
- checks,
59
- passed: blockers.length === 0,
60
- warnings
115
+ ambiguous: true,
116
+ names,
117
+ recognized: names.size > 0
61
118
  };
62
- }
63
- function clusterNamespace(value, configuredNamespace) {
64
- return typeof value === "string" && value.length > 0 ? value : configuredNamespace ?? defaultClusterDoctorNamespace();
65
- }
66
- function checkCommand(name, args, cwd) {
67
- return checkCommandWithRunner(name, name, args, cwd);
68
- }
69
- async function checkCommandWithRunner(name, command, args, cwd) {
119
+ };
120
+ const interactivePermissionPaths = (value, path = ["permission"]) => {
121
+ if (typeof value === "string") return HEADLESS_AGENT_PERMISSION_VALUES.has(value.toLowerCase()) ? [path.join(".")] : [];
122
+ if (Array.isArray(value)) return value.flatMap((item, index) => interactivePermissionPaths(item, [...path, String(index)]));
123
+ if (!isRecord(value)) return [];
124
+ return Object.entries(value).flatMap(([key, item]) => interactivePermissionPaths(item, [...path, key]));
125
+ };
126
+ const isHeadless = () => {
127
+ const ci = process.env.CI?.toLowerCase();
128
+ return ci !== void 0 && ci !== "" && ci !== "0" && ci !== "false" || !process.stdin.isTTY;
129
+ };
130
+ const stringField = (value, field) => {
131
+ if (typeof value !== "object" || value === null) return "";
132
+ const candidate = Reflect.get(value, field);
133
+ return typeof candidate === "string" ? candidate : "";
134
+ };
135
+ const commandErrorDetail = (err) => {
136
+ return ([
137
+ stringField(err, "shortMessage"),
138
+ stringField(err, "stderr"),
139
+ stringField(err, "message"),
140
+ String(err)
141
+ ].find((candidate) => candidate.trim() !== "") ?? "not available").trim();
142
+ };
143
+ const checkCommandWithRunner = async (name, command, args, cwd) => {
70
144
  try {
71
145
  await execa(command, args, {
72
146
  cwd,
@@ -77,34 +151,16 @@ async function checkCommandWithRunner(name, command, args, cwd) {
77
151
  name,
78
152
  passed: true
79
153
  };
80
- } catch (err) {
154
+ } catch (error) {
81
155
  return {
82
- detail: commandErrorDetail(err),
156
+ detail: commandErrorDetail(error),
83
157
  name,
84
158
  passed: false
85
159
  };
86
160
  }
87
- }
88
- function checkPipelineConfig(cwd) {
89
- try {
90
- loadPipelineConfig(cwd);
91
- return {
92
- detail: "valid",
93
- name: "pipeline-config",
94
- passed: true
95
- };
96
- } catch (err) {
97
- let message = "invalid";
98
- if (err instanceof PipelineConfigError) message = err.issues.map((issue) => issue.message).join("; ");
99
- else if (err instanceof Error) message = err.message;
100
- return {
101
- detail: message || "missing or invalid",
102
- name: "pipeline-config",
103
- passed: false
104
- };
105
- }
106
- }
107
- async function checkOpenCodeSdk() {
161
+ };
162
+ const checkCommand = async (name, args, cwd) => await checkCommandWithRunner(name, name, args, cwd);
163
+ const checkOpenCodeSdk = async () => {
108
164
  try {
109
165
  if (typeof (await import("@opencode-ai/sdk")).createOpencodeClient !== "function") return {
110
166
  detail: "@opencode-ai/sdk does not export createOpencodeClient",
@@ -116,15 +172,15 @@ async function checkOpenCodeSdk() {
116
172
  name: "opencode-sdk",
117
173
  passed: true
118
174
  };
119
- } catch (err) {
175
+ } catch (error) {
120
176
  return {
121
- detail: commandErrorDetail(err),
177
+ detail: commandErrorDetail(error),
122
178
  name: "opencode-sdk",
123
179
  passed: false
124
180
  };
125
181
  }
126
- }
127
- async function checkMokaAgents(cwd, config) {
182
+ };
183
+ const checkMokaAgents = async (cwd, config) => {
128
184
  const expected = expectedRunAgentNames(config);
129
185
  if (expected.length === 0) return { check: {
130
186
  detail: "no configured MoKa run agents",
@@ -132,10 +188,11 @@ async function checkMokaAgents(cwd, config) {
132
188
  passed: true
133
189
  } };
134
190
  try {
135
- const visible = visibleAgentNames((await execa("opencode", OPENCODE_AGENT_LIST_ARGS, {
191
+ const result = await execa("opencode", OPENCODE_AGENT_LIST_ARGS, {
136
192
  cwd,
137
193
  stdin: "ignore"
138
- })).stdout);
194
+ });
195
+ const visible = visibleAgentNames(result.stdout);
139
196
  if (!visible.recognized) return skippedAgentVisibility("OpenCode agent listing output was not recognized");
140
197
  if (visible.ambiguous && expected.every((name) => !visible.names.has(name))) return skippedAgentVisibility("OpenCode agent listing output did not include recognizable MoKa agent names");
141
198
  const missing = expected.filter((name) => !visible.names.has(name));
@@ -148,121 +205,69 @@ async function checkMokaAgents(cwd, config) {
148
205
  name: "moka-agents",
149
206
  passed: true
150
207
  } };
151
- } catch (err) {
152
- return skippedAgentVisibility(`Could not cheaply list OpenCode agents: ${commandErrorDetail(err)}`);
208
+ } catch (error) {
209
+ return skippedAgentVisibility(`Could not cheaply list OpenCode agents: ${commandErrorDetail(error)}`);
153
210
  }
154
- }
155
- function skippedAgentVisibility(detail) {
156
- return {
157
- check: {
158
- detail: "skipped because OpenCode agent listing is unavailable",
159
- name: "moka-agents",
160
- passed: true
161
- },
162
- warning: {
163
- detail,
164
- name: "moka-agents",
165
- passed: true
166
- }
167
- };
168
- }
169
- function expectedRunAgentNames(config) {
170
- const profiles = /* @__PURE__ */ new Set();
171
- for (const catalog of Object.values(config.scheduler.node_catalogs)) for (const node of Object.values(catalog.nodes)) if (RUN_READINESS_CATEGORIES.has(node.category)) profiles.add(node.profile);
172
- return [...profiles].filter((profileId) => config.profiles[profileId]?.runner === "opencode").map(opencodeAgentName).sort((a, b) => a.localeCompare(b));
173
- }
174
- function visibleAgentNames(stdout) {
175
- const names = /* @__PURE__ */ new Set();
176
- try {
177
- const parsed = JSON.parse(stdout);
178
- return {
179
- ambiguous: false,
180
- names,
181
- recognized: collectAgentNames(parsed, names, Array.isArray(parsed))
182
- };
183
- } catch {
184
- for (const line of stdout.split(LINE_RE)) {
185
- const name = line.trim().replace(BULLET_PREFIX_RE, "");
186
- if (name) names.add(name);
187
- }
188
- }
189
- return {
190
- ambiguous: true,
191
- names,
192
- recognized: names.size > 0
193
- };
194
- }
195
- function collectAgentNames(value, names, inAgentList) {
196
- if (typeof value === "string") {
197
- if (inAgentList) names.add(value);
198
- return inAgentList;
199
- }
200
- if (Array.isArray(value)) {
201
- let recognized = inAgentList;
202
- for (const item of value) recognized = collectAgentNames(item, names, inAgentList) || recognized;
203
- return recognized;
204
- }
205
- if (!(value && typeof value === "object")) return false;
206
- const record = value;
207
- let recognized = false;
208
- for (const key of [
209
- "agent",
210
- "id",
211
- "name",
212
- "subagent_type",
213
- "title"
214
- ]) if (typeof record[key] === "string") {
215
- names.add(record[key]);
216
- recognized = true;
217
- }
218
- for (const key of [
219
- "agents",
220
- "data",
221
- "items",
222
- "result"
223
- ]) {
224
- const item = record[key];
225
- if (Array.isArray(item) || item && typeof item === "object") recognized = collectAgentNames(item, names, true) || recognized;
226
- }
227
- return recognized;
228
- }
229
- function headlessPermissionWarnings(cwd) {
230
- if (!isHeadless()) return [];
231
- const agentDir = join(cwd, ".opencode", "agents");
232
- if (!existsSync(agentDir)) return [];
233
- return readdirSync(agentDir).filter((entry) => entry.endsWith(".md")).flatMap((entry) => headlessPermissionWarning(join(agentDir, entry), entry));
234
- }
235
- function headlessPermissionWarning(path, entry) {
211
+ };
212
+ const headlessPermissionWarning = (path, entry) => {
236
213
  try {
237
214
  if (!statSync(path).isFile()) return [];
238
- const risky = interactivePermissionPaths(matter(readFileSync(path, "utf8")).data.permission);
215
+ const parsed = matter(readFileSync(path, "utf-8"));
216
+ const risky = interactivePermissionPaths(parsed.data.permission);
239
217
  if (risky.length === 0) return [];
240
218
  return [{
241
219
  detail: `${entry} requires interactive permission prompts at ${risky.join(", ")}; headless MoKa runs may block.`,
242
220
  name: "headless-permissions",
243
221
  passed: true
244
222
  }];
245
- } catch (err) {
223
+ } catch (error) {
246
224
  return [{
247
- detail: `Could not inspect ${entry} for headless permission risks: ${commandErrorDetail(err)}`,
225
+ detail: `Could not inspect ${entry} for headless permission risks: ${commandErrorDetail(error)}`,
248
226
  name: "headless-permissions",
249
227
  passed: true
250
228
  }];
251
229
  }
252
- }
253
- function interactivePermissionPaths(value, path = ["permission"]) {
254
- if (typeof value === "string") return HEADLESS_AGENT_PERMISSION_VALUES.has(value.toLowerCase()) ? [path.join(".")] : [];
255
- if (Array.isArray(value)) return value.flatMap((item, index) => interactivePermissionPaths(item, [...path, String(index)]));
256
- if (!(value && typeof value === "object")) return [];
257
- return Object.entries(value).flatMap(([key, item]) => interactivePermissionPaths(item, [...path, key]));
258
- }
259
- function isHeadless() {
260
- const ci = process.env.CI?.toLowerCase();
261
- return ci !== void 0 && ci !== "" && ci !== "0" && ci !== "false" || !process.stdin.isTTY;
262
- }
263
- function commandErrorDetail(err) {
264
- const error = err;
265
- return (error.shortMessage || error.stderr || error.message || String(err)).trim() || "not available";
266
- }
230
+ };
231
+ const headlessPermissionWarnings = (cwd) => {
232
+ if (!isHeadless()) return [];
233
+ const agentDir = join(cwd, ".opencode", "agents");
234
+ if (!existsSync(agentDir)) return [];
235
+ return readdirSync(agentDir).filter((entry) => entry.endsWith(".md")).flatMap((entry) => headlessPermissionWarning(join(agentDir, entry), entry));
236
+ };
237
+ const runDoctor = async (cwd, options = {}) => {
238
+ const commandChecks = await Promise.all([
239
+ checkCommand("npx", ["--version"], cwd),
240
+ checkCommand("opencode", ["--version"], cwd),
241
+ checkCommand("fallow", ["--version"], cwd)
242
+ ]);
243
+ const configCheck = checkPipelineConfig(cwd);
244
+ const config = configCheck.passed ? loadPipelineConfig(cwd) : null;
245
+ const [sdkCheck, agentVisibility] = await Promise.all([checkOpenCodeSdk(), config ? checkMokaAgents(cwd, config) : Promise.resolve({ check: {
246
+ detail: "skipped because pipeline config is invalid",
247
+ name: "moka-agents",
248
+ passed: true
249
+ } })]);
250
+ const globalConfig = loadMokaGlobalConfig();
251
+ const clusterResult = options.cluster !== void 0 && options.cluster !== false ? await runClusterDoctor({
252
+ kubeContext: options.kubeContext,
253
+ kubeconfigPath: options.kubeconfig ?? (globalConfig === null ? void 0 : globalConfig.momokaya.kubernetes.kubeconfig),
254
+ namespace: clusterNamespace(options.cluster, globalConfig === null ? void 0 : globalConfig.momokaya.kubernetes.namespace)
255
+ }) : { checks: [] };
256
+ const checks = [
257
+ ...commandChecks,
258
+ configCheck,
259
+ sdkCheck,
260
+ agentVisibility.check,
261
+ ...clusterResult.checks
262
+ ];
263
+ const warnings = [...agentVisibility.warning ? [agentVisibility.warning] : [], ...headlessPermissionWarnings(cwd)];
264
+ const blockers = checks.filter((check) => !check.passed);
265
+ return {
266
+ blockers,
267
+ checks,
268
+ passed: blockers.length === 0,
269
+ warnings
270
+ };
271
+ };
267
272
  //#endregion
268
273
  export { runDoctor };
@@ -2,32 +2,32 @@ import { runCreateExperiment } from "../factory/create-experiment.js";
2
2
  import { runTemplateUpdate, summarizeTemplateUpdate } from "../factory/template-update.js";
3
3
  import { Option } from "commander";
4
4
  //#region src/cli/factory-commands.ts
5
- function registerFactoryCommands(program) {
6
- program.command("create-experiment").description("Birth a fleet experiment: copier-stamp momokaya-template, create+push the org repo, register it in infra's fleet registry").requiredOption("--name <name>", "app name (kebab-case)").addOption(new Option("--flavor <flavor>", "app flavor").choices(["web", "expo-web"]).default("web")).option("--no-db", "skip the database surface").option("--no-previews", "skip per-PR preview environments").option("--org <org>", "GitHub org for the new repo").option("--template-src <source>", "copier template source").option("--template-ref <ref>", "template tag/ref (default: latest tag)").option("--infra-repo-url <url>", "infra repo the registry entry lands in").action(async (flags) => {
5
+ const hasText = (value) => typeof value === "string" && value !== "";
6
+ const registerFactoryCommands = (program) => {
7
+ program.command("create-experiment").description("Birth a fleet experiment: copier-stamp momokaya-template, create+push the org repo, register it in infra's fleet registry").requiredOption("--name <name>", "app name (kebab-case)").addOption(new Option("--flavor <flavor>", "app flavor").choices(["web", "expo-web"]).default("web")).option("--no-db", "skip the database surface").option("--org <org>", "GitHub org for the new repo").option("--template-src <source>", "copier template source").option("--template-ref <ref>", "template tag/ref (default: latest tag)").option("--infra-repo-url <url>", "infra repo the registry entry lands in").action(async (flags) => {
7
8
  const result = await runCreateExperiment({
8
9
  db: flags.db,
9
10
  flavor: flags.flavor,
10
- ...flags.infraRepoUrl ? { infraRepoUrl: flags.infraRepoUrl } : {},
11
+ ...hasText(flags.infraRepoUrl) ? { infraRepoUrl: flags.infraRepoUrl } : {},
11
12
  name: flags.name,
12
- ...flags.org ? { org: flags.org } : {},
13
- previews: flags.previews,
14
- ...flags.templateRef ? { templateRef: flags.templateRef } : {},
15
- ...flags.templateSrc ? { templateSource: flags.templateSrc } : {}
13
+ ...hasText(flags.org) ? { org: flags.org } : {},
14
+ ...hasText(flags.templateRef) ? { templateRef: flags.templateRef } : {},
15
+ ...hasText(flags.templateSrc) ? { templateSource: flags.templateSrc } : {}
16
16
  });
17
17
  console.log(`Experiment born: ${result.repoUrl} (registry ${result.registryPath} @ infra ${result.infraCommitSha})`);
18
18
  });
19
19
  program.command("template-update").description("Fan copier-update PRs out across repos stamped from momokaya-template").option("--repos <repos>", "comma-separated repo list (skips fleet-registry discovery)").option("--org <org>", "GitHub org the stamped repos live in").option("--template-match <substring>", "answers-file _src_path filter for stamp detection").option("--template-ref <ref>", "template tag/ref (default: latest tag)").option("--infra-repo-url <url>", "infra repo used for discovery").action(async (flags) => {
20
20
  const { results } = await runTemplateUpdate({
21
- ...flags.infraRepoUrl ? { infraRepoUrl: flags.infraRepoUrl } : {},
22
- ...flags.org ? { org: flags.org } : {},
23
- ...flags.repos ? { repos: flags.repos.split(",").map((repo) => repo.trim()).filter((repo) => repo.length > 0) } : {},
24
- ...flags.templateMatch ? { templateMatch: flags.templateMatch } : {},
25
- ...flags.templateRef ? { templateRef: flags.templateRef } : {}
21
+ ...hasText(flags.infraRepoUrl) ? { infraRepoUrl: flags.infraRepoUrl } : {},
22
+ ...hasText(flags.org) ? { org: flags.org } : {},
23
+ ...hasText(flags.repos) ? { repos: flags.repos.split(",").map((repo) => repo.trim()).filter((repo) => repo.length > 0) } : {},
24
+ ...hasText(flags.templateMatch) ? { templateMatch: flags.templateMatch } : {},
25
+ ...hasText(flags.templateRef) ? { templateRef: flags.templateRef } : {}
26
26
  });
27
27
  const { failed, opened } = summarizeTemplateUpdate(results);
28
28
  console.log(`template-update: ${opened} PR(s) opened, ${failed} error(s)`);
29
29
  if (failed > 0) process.exitCode = 1;
30
30
  });
31
- }
31
+ };
32
32
  //#endregion
33
33
  export { registerFactoryCommands };