@webpresso/agent-kit 3.1.28 → 3.2.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.

Potentially problematic release.


This version of @webpresso/agent-kit might be problematic. Click here for more details.

Files changed (329) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +179 -19
  3. package/catalog/AGENTS.md.tpl +6 -6
  4. package/catalog/agent/agents/implementer.md +45 -0
  5. package/catalog/agent/rules/ci-cost-local-first.md +139 -0
  6. package/catalog/agent/rules/ci-test-perf.md +5 -1
  7. package/catalog/agent/rules/cmd-execution.md +9 -0
  8. package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
  9. package/catalog/agent/rules/pre-implementation.md +30 -2
  10. package/catalog/agent/rules/supported-agent-clis.md +9 -0
  11. package/catalog/agent/rules/test-scan-perf.md +80 -0
  12. package/catalog/agent/rules/typescript-version.md +50 -0
  13. package/catalog/agent/rules/workflow-skills-routing.md +4 -2
  14. package/catalog/agent/skills/codex/SKILL.md +25 -1
  15. package/catalog/agent/skills/fix/SKILL.md +3 -0
  16. package/catalog/agent/skills/verify/SKILL.md +22 -2
  17. package/catalog/docs/templates/blueprint.md +1 -1
  18. package/dist/esm/audit/agents.js +1 -0
  19. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
  20. package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
  21. package/dist/esm/audit/blueprint-pr-coverage.js +72 -7
  22. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  23. package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
  24. package/dist/esm/audit/ci-test-perf.js +30 -0
  25. package/dist/esm/audit/command-surface.js +4 -2
  26. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  27. package/dist/esm/audit/host-substitution-risk.js +256 -0
  28. package/dist/esm/audit/registry.d.ts +4 -4
  29. package/dist/esm/audit/registry.js +11 -1
  30. package/dist/esm/audit/roadmap-links.js +6 -0
  31. package/dist/esm/audit/test-scan-perf.d.ts +18 -0
  32. package/dist/esm/audit/test-scan-perf.js +275 -0
  33. package/dist/esm/audit/toolchain-isolation.js +1 -2
  34. package/dist/esm/audit/typescript-version.d.ts +2 -0
  35. package/dist/esm/audit/typescript-version.js +123 -0
  36. package/dist/esm/blueprint/core/parser.js +43 -1
  37. package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
  38. package/dist/esm/blueprint/core/validation/criteria.js +41 -2
  39. package/dist/esm/blueprint/core/validation/state.js +25 -14
  40. package/dist/esm/blueprint/db/migrations/run.js +4 -2
  41. package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
  42. package/dist/esm/blueprint/lifecycle/audit.js +65 -10
  43. package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
  44. package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
  45. package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
  46. package/dist/esm/blueprint/lifecycle/engine.js +53 -6
  47. package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
  48. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  49. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  50. package/dist/esm/blueprint/lifecycle/review-provenance.js +118 -12
  51. package/dist/esm/blueprint/markdown/helpers.js +6 -2
  52. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  53. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  54. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  55. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  56. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  57. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  58. package/dist/esm/blueprint/trust/gates.js +20 -0
  59. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  60. package/dist/esm/blueprint/trust/promotion.js +62 -112
  61. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  62. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  63. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  64. package/dist/esm/blueprint/trust/validator.js +7 -1
  65. package/dist/esm/blueprint/utils/archive.js +27 -5
  66. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  67. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  68. package/dist/esm/build/cli-mcp-parity.js +10 -0
  69. package/dist/esm/ci/act-replay.js +110 -7
  70. package/dist/esm/ci/act-runner.d.ts +4 -0
  71. package/dist/esm/ci/act-runner.js +85 -26
  72. package/dist/esm/ci/act-worktree-git.d.ts +11 -0
  73. package/dist/esm/ci/act-worktree-git.js +219 -0
  74. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  75. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  76. package/dist/esm/cli/cli.d.ts +1 -1
  77. package/dist/esm/cli/cli.js +34 -0
  78. package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
  79. package/dist/esm/cli/commands/agent-launch.js +82 -4
  80. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  81. package/dist/esm/cli/commands/audit-core.js +8 -1
  82. package/dist/esm/cli/commands/audit.js +20 -5
  83. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  84. package/dist/esm/cli/commands/blueprint/mutations.js +83 -47
  85. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  86. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  87. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  88. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  89. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  90. package/dist/esm/cli/commands/blueprint/router.js +40 -3
  91. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  92. package/dist/esm/cli/commands/ci-preflight.js +6 -3
  93. package/dist/esm/cli/commands/ci.d.ts +8 -1
  94. package/dist/esm/cli/commands/ci.js +80 -21
  95. package/dist/esm/cli/commands/compile.js +5 -31
  96. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  97. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  98. package/dist/esm/cli/commands/dash/keymap.js +1 -0
  99. package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
  100. package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
  101. package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
  102. package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
  103. package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
  104. package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
  105. package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
  106. package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
  107. package/dist/esm/cli/commands/dash/plan-once.d.ts +22 -2
  108. package/dist/esm/cli/commands/dash/plan-once.js +41 -4
  109. package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
  110. package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
  111. package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
  112. package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
  113. package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
  114. package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
  115. package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
  116. package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
  117. package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
  118. package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
  119. package/dist/esm/cli/commands/dash/tui/runtime.js +239 -4
  120. package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
  121. package/dist/esm/cli/commands/dash/tui/state.js +257 -4
  122. package/dist/esm/cli/commands/doctor.js +5 -0
  123. package/dist/esm/cli/commands/format.js +0 -2
  124. package/dist/esm/cli/commands/hook.d.ts +5 -0
  125. package/dist/esm/cli/commands/hook.js +15 -9
  126. package/dist/esm/cli/commands/init/config.d.ts +0 -1
  127. package/dist/esm/cli/commands/init/config.js +3 -6
  128. package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
  129. package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
  130. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  131. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  132. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  133. package/dist/esm/cli/commands/init/convergence.js +57 -0
  134. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  135. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  136. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  137. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  138. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  139. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  140. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  141. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  142. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  143. package/dist/esm/cli/commands/init/index.d.ts +1 -0
  144. package/dist/esm/cli/commands/init/index.js +326 -173
  145. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  146. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  147. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  148. package/dist/esm/cli/commands/init/merge.js +30 -1
  149. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  150. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  151. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  152. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  153. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
  154. package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
  155. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
  156. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
  157. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  158. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  159. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  160. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
  161. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
  162. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
  163. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
  164. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
  165. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  166. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +297 -173
  167. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
  168. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
  169. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  170. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  171. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  172. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  173. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  174. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  175. package/dist/esm/cli/commands/logs.d.ts +15 -0
  176. package/dist/esm/cli/commands/logs.js +192 -3
  177. package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
  178. package/dist/esm/cli/commands/opencode-rotate.js +135 -0
  179. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  180. package/dist/esm/cli/commands/public-readiness.js +42 -0
  181. package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
  182. package/dist/esm/cli/commands/quality-log-store.js +187 -51
  183. package/dist/esm/cli/commands/review.d.ts +35 -3
  184. package/dist/esm/cli/commands/review.js +734 -217
  185. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  186. package/dist/esm/cli/commands/run-wait.js +122 -0
  187. package/dist/esm/cli/commands/secrets.d.ts +1 -0
  188. package/dist/esm/cli/commands/secrets.js +4 -1
  189. package/dist/esm/cli/commands/setup-help.js +1 -1
  190. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  191. package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
  192. package/dist/esm/cli/direct-provider-launch.js +31 -3
  193. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  194. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  195. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  196. package/dist/esm/daemon/domains/git-actions.js +136 -0
  197. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  198. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  199. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  200. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  201. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  202. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  203. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  204. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  205. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  206. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  207. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  208. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  209. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  210. package/dist/esm/daemon/handlers/product.js +10 -119
  211. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  212. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  213. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  214. package/dist/esm/daemon/handlers/shared.js +16 -0
  215. package/dist/esm/daemon/methods.d.ts +8 -1
  216. package/dist/esm/daemon/methods.js +7 -0
  217. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  218. package/dist/esm/daemon/protocol/contract.js +26 -0
  219. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  220. package/dist/esm/daemon/protocol/index.js +1 -1
  221. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  222. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  223. package/dist/esm/hooks/conformance/batch.js +5 -0
  224. package/dist/esm/hooks/doctor.d.ts +15 -0
  225. package/dist/esm/hooks/doctor.js +79 -8
  226. package/dist/esm/hooks/permission-request/index.js +9 -3
  227. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  228. package/dist/esm/hooks/permission-request/policy.js +13 -0
  229. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  230. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  231. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  232. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  233. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  234. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  235. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  236. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +58 -4
  237. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  238. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  239. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
  240. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
  241. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  242. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  243. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
  244. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
  245. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  246. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  247. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  248. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  249. package/dist/esm/hooks/shared/types.d.ts +8 -5
  250. package/dist/esm/hooks/shared/types.js +23 -8
  251. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  252. package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
  253. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  254. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
  255. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  256. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  257. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  258. package/dist/esm/mcp/blueprint/registration.js +3 -2
  259. package/dist/esm/mcp/server.d.ts +1 -0
  260. package/dist/esm/mcp/server.js +27 -0
  261. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  262. package/dist/esm/mcp/tools/_names.js +5 -0
  263. package/dist/esm/mcp/tools/_registry.js +10 -0
  264. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  265. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  266. package/dist/esm/mcp/tools/audit.d.ts +5 -1
  267. package/dist/esm/mcp/tools/audit.js +40 -0
  268. package/dist/esm/mcp/tools/audits.d.ts +4 -0
  269. package/dist/esm/mcp/tools/ci-act.js +9 -11
  270. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  271. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  272. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  273. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  274. package/dist/esm/mcp/tools/review-run.js +216 -0
  275. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  276. package/dist/esm/mcp/tools/run-wait.js +593 -0
  277. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  278. package/dist/esm/mcp/tools/session-id.js +45 -0
  279. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  280. package/dist/esm/mcp/tools/session-info.js +49 -0
  281. package/dist/esm/output-transforms/audit.d.ts +23 -0
  282. package/dist/esm/output-transforms/audit.js +47 -0
  283. package/dist/esm/output-transforms/index.js +2 -0
  284. package/dist/esm/paths/state-root.js +3 -0
  285. package/dist/esm/review/authority.js +30 -7
  286. package/dist/esm/review/availability.d.ts +22 -0
  287. package/dist/esm/review/availability.js +123 -23
  288. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  289. package/dist/esm/review/delivery-verifier.js +115 -0
  290. package/dist/esm/review/events.d.ts +3 -2
  291. package/dist/esm/review/events.js +23 -3
  292. package/dist/esm/review/execution/adapters.js +26 -9
  293. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  294. package/dist/esm/review/execution/output-policy.js +12 -0
  295. package/dist/esm/review/execution/review-checkout.js +87 -6
  296. package/dist/esm/review/execution/supervisor.js +5 -0
  297. package/dist/esm/review/execution/types.d.ts +8 -0
  298. package/dist/esm/review/opencode-account.d.ts +24 -0
  299. package/dist/esm/review/opencode-account.js +68 -0
  300. package/dist/esm/review/opencode-auth.d.ts +33 -0
  301. package/dist/esm/review/opencode-auth.js +79 -0
  302. package/dist/esm/review/subject.d.ts +27 -0
  303. package/dist/esm/review/subject.js +179 -32
  304. package/dist/esm/runtime/executor.d.ts +1 -0
  305. package/dist/esm/runtime/executor.js +2 -1
  306. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  307. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  308. package/dist/esm/secret-gate/runner.d.ts +1 -0
  309. package/dist/esm/secret-gate/runner.js +4 -1
  310. package/dist/esm/status/snapshot.js +30 -19
  311. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  312. package/dist/esm/test/duration-sequencer.js +54 -0
  313. package/dist/esm/test/shard-durations.json +922 -0
  314. package/dist/esm/test/worker-budget.d.ts +6 -0
  315. package/dist/esm/test/worker-budget.js +59 -1
  316. package/dist/esm/test-helpers/global-setup.d.ts +1 -1
  317. package/dist/esm/test-helpers/global-setup.js +68 -24
  318. package/dist/esm/ultragoal/service.d.ts +7 -0
  319. package/dist/esm/ultragoal/service.js +42 -2
  320. package/dist/esm/utils/write-json-file.js +3 -1
  321. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  322. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  323. package/dist/esm/worktrees/current.d.ts +1 -0
  324. package/dist/esm/worktrees/current.js +1 -0
  325. package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
  326. package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
  327. package/dist/esm/worktrees/registry.d.ts +8 -0
  328. package/dist/esm/worktrees/registry.js +18 -3
  329. package/package.json +18 -15
@@ -1,7 +1,8 @@
1
- import { buildPublicCiActCommand, resolveCiActExecutionMode, sanitizePublicCiActArgv, } from "#ci/act-runner.js";
1
+ import { buildPublicCiActCommand, preparePublicCiActExecution, resolveCiActExecutionMode, resolveCiActSecretEnvProfile, sanitizePublicCiActArgv, shouldForceCiActSecretGate, } from "#ci/act-runner.js";
2
2
  import { redactText } from "#mcp/tools/_shared/redact.js";
3
3
  import { runSecretGateCommand } from "#secret-gate/runner.js";
4
4
  export const DEFAULT_CI_ACT_TIMEOUT_MS = 20 * 60_000;
5
+ export const SMOKE_CI_ACT_TIMEOUT_MS = 30_000;
5
6
  export const MAX_CI_ACT_TIMEOUT_MS = 60 * 60_000;
6
7
  export const CI_COMMAND_HELP = [
7
8
  "Run repository CI helpers through the portable, secret-safe wp surface.",
@@ -10,6 +11,8 @@ export const CI_COMMAND_HELP = [
10
11
  "Examples:",
11
12
  " wp ci act --workflow ci-e2e",
12
13
  " wp ci act --workflow ci-e2e --execute",
14
+ " wp ci act --workflow ci-e2e --execute --smoke",
15
+ " wp ci act --workflow ci-e2e --job test --matrix target:beta",
13
16
  " wp ci act --workflow-path .github/workflows/ci.yml --job test",
14
17
  ].join("\n");
15
18
  export function registerCiCommand(cli) {
@@ -20,6 +23,7 @@ export function registerCiCommand(cli) {
20
23
  })
21
24
  .option("--workflow-path <path>", "Explicit workflow file path")
22
25
  .option("--job <id>", "Workflow job id")
26
+ .option("--matrix <key:value>", "Run only matching act matrix selector(s) (repeatable)")
23
27
  .option("--event-name <name>", "act event name: pull_request | push | workflow_dispatch")
24
28
  .option("--event-path <path>", "Use an existing event JSON file")
25
29
  .option("--env-profile <profile>", "Secret-gate runtime profile", { default: "secrets-only" })
@@ -28,6 +32,7 @@ export function registerCiCommand(cli) {
28
32
  .option("--container-architecture <arch>", "act container architecture override")
29
33
  .option("--platform-image <image>", "act runner image for ubicloud-standard-2")
30
34
  .option("--timeout-ms <ms>", `act execution timeout in milliseconds (default: ${DEFAULT_CI_ACT_TIMEOUT_MS})`)
35
+ .option("--smoke", `Use a short ${SMOKE_CI_ACT_TIMEOUT_MS}ms timeout for targeted local proof`)
31
36
  .option("--execute", "Run act; default is a redacted dry-run preview")
32
37
  .option("--dry-run", "Print the resolved command without executing it")
33
38
  .action((action, flags) => {
@@ -39,14 +44,16 @@ export function registerCiCommand(cli) {
39
44
  workflow: flags.workflow,
40
45
  workflowPath: flags.workflowPath,
41
46
  job: flags.job,
42
- eventName: flags.eventName,
47
+ matrix: parseCiActMatrixFlags(flags.matrix),
48
+ eventName: parseCiActEventName(flags.eventName),
43
49
  envProfile: flags.envProfile,
44
50
  secretProfile: flags.secretProfile,
45
- mode: flags.mode,
51
+ mode: parseCiActExecutionMode(flags.mode),
46
52
  containerArchitecture: flags.containerArchitecture,
47
53
  platformImage: flags.platformImage,
48
54
  eventPath: flags.eventPath,
49
55
  execute: Boolean(flags.execute) && !flags.dryRun,
56
+ smoke: Boolean(flags.smoke),
50
57
  timeoutMs: parseCiActTimeoutMs(flags.timeoutMs),
51
58
  });
52
59
  });
@@ -60,8 +67,8 @@ export function validateCiActCommand(..._legacyArgs) {
60
67
  }
61
68
  export async function runCiActCommand(options = {}, deps = {}) {
62
69
  const cwd = deps.cwd ?? process.cwd();
63
- const command = buildPublicCiActCommand({ ...options, cwd });
64
70
  if (!options.execute) {
71
+ const command = buildPublicCiActCommand({ ...options, cwd });
65
72
  const preview = sanitizePublicCiActArgv(command);
66
73
  (deps.stdout ?? process.stdout).write(`${JSON.stringify({ command: preview.command, args: preview.args })}\n`);
67
74
  return 0;
@@ -69,23 +76,34 @@ export async function runCiActCommand(options = {}, deps = {}) {
69
76
  if (resolveCiActExecutionMode(options) === "replay") {
70
77
  (deps.stderr ?? process.stderr).write("Warning: replay mode is a generated local approximation and is not security-equivalent to GitHub CI or OIDC.\n");
71
78
  }
72
- const result = await (deps.run ?? runSecretGateCommand)({
73
- cwd,
74
- envProfile: "none",
75
- command: command.command,
76
- args: command.args,
77
- timeoutMs: normalizeCiActTimeoutMs(options.timeoutMs),
78
- });
79
- const stdout = redactText(result.stdout) ?? "";
80
- const stderr = redactText(result.stderr) ?? "";
81
- if (stdout)
82
- (deps.stdout ?? process.stdout).write(stdout);
83
- if (stderr)
84
- (deps.stderr ?? process.stderr).write(stderr);
85
- return result.exitCode;
79
+ const prepared = preparePublicCiActExecution({ ...options, cwd });
80
+ try {
81
+ const secretProfile = resolveCiActSecretEnvProfile({ ...options, cwd }) ?? "preview";
82
+ const result = await (deps.run ?? runSecretGateCommand)({
83
+ cwd,
84
+ commandCwd: prepared.cwd,
85
+ sink: "act",
86
+ profile: secretProfile,
87
+ envProfile: options.envProfile ?? "secrets-only",
88
+ forceSecretGate: shouldForceCiActSecretGate(options.envProfile),
89
+ command: "act",
90
+ args: prepared.command.actArgs,
91
+ timeoutMs: normalizeCiActTimeoutMs(options.timeoutMs, { smoke: options.smoke }),
92
+ });
93
+ const stdout = redactText(result.stdout) ?? "";
94
+ const stderr = redactText(result.stderr) ?? "";
95
+ if (stdout)
96
+ (deps.stdout ?? process.stdout).write(stdout);
97
+ if (stderr)
98
+ (deps.stderr ?? process.stderr).write(stderr);
99
+ return result.exitCode;
100
+ }
101
+ finally {
102
+ prepared.cleanup();
103
+ }
86
104
  }
87
- export function normalizeCiActTimeoutMs(value) {
88
- const timeoutMs = value ?? DEFAULT_CI_ACT_TIMEOUT_MS;
105
+ export function normalizeCiActTimeoutMs(value, options = {}) {
106
+ const timeoutMs = value ?? (options.smoke ? SMOKE_CI_ACT_TIMEOUT_MS : DEFAULT_CI_ACT_TIMEOUT_MS);
89
107
  if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
90
108
  throw new Error("--timeout-ms must be a positive integer");
91
109
  }
@@ -100,7 +118,48 @@ export function parseCiActTimeoutMs(value) {
100
118
  const parsed = typeof value === "number"
101
119
  ? value
102
120
  : typeof value === "string"
103
- ? Number.parseInt(value, 10)
121
+ ? parseCiActTimeoutString(value)
104
122
  : Number.NaN;
105
123
  return normalizeCiActTimeoutMs(parsed);
106
124
  }
125
+ export function parseCiActExecutionMode(value) {
126
+ if (value === undefined || value === null || value === "")
127
+ return undefined;
128
+ if (value === "direct" || value === "replay")
129
+ return value;
130
+ throw new Error('--mode must be "direct" or "replay"');
131
+ }
132
+ export function parseCiActEventName(value) {
133
+ if (value === undefined || value === null || value === "")
134
+ return undefined;
135
+ if (value === "pull_request" || value === "push" || value === "workflow_dispatch")
136
+ return value;
137
+ throw new Error('--event-name must be "pull_request", "push", or "workflow_dispatch"');
138
+ }
139
+ function parseCiActTimeoutString(value) {
140
+ const trimmed = value.trim();
141
+ if (!/^[1-9]\d*$/u.test(trimmed)) {
142
+ throw new Error("--timeout-ms must be a positive integer");
143
+ }
144
+ return Number(trimmed);
145
+ }
146
+ function parseCiActMatrixFlags(value) {
147
+ if (value === undefined)
148
+ return [];
149
+ if (typeof value === "string") {
150
+ if (value.trim().length === 0) {
151
+ throw new Error("--matrix must be provided as one or more key:value selectors");
152
+ }
153
+ return [value];
154
+ }
155
+ if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
156
+ if (value.some((entry) => entry.trim().length === 0)) {
157
+ throw new Error("--matrix must be provided as one or more key:value selectors");
158
+ }
159
+ return [...value];
160
+ }
161
+ if (value === true) {
162
+ throw new Error("--matrix must be provided as one or more key:value selectors");
163
+ }
164
+ throw new Error("--matrix must be provided as one or more key:value selectors");
165
+ }
@@ -247,22 +247,10 @@ export async function runCompile(options) {
247
247
  message: `wp compile: rulesync exited with code ${exitCode}`,
248
248
  };
249
249
  }
250
- // Step 4: Emit plugin manifests in parallel
251
- const skillNames = Object.keys(assets.skills);
252
- const commandNames = Object.keys(assets.commands);
253
- const pkgVersion = readPackageVersion(cwd);
254
- const manifestOpts = {
255
- agentDir,
256
- outDir: cwd,
257
- version: pkgVersion,
258
- skills: skillNames,
259
- commands: commandNames,
260
- };
261
- await Promise.all([
262
- import("#compiler/manifests/claude").then((m) => m.emitManifest(manifestOpts)),
263
- import("#compiler/manifests/codex").then((m) => m.emitManifest(manifestOpts)),
264
- ]);
265
- // Step 5: Run mergeAgentsMd to produce AGENTS.md at repo root
250
+ // Step 4: Run mergeAgentsMd to produce AGENTS.md at repo root
251
+ // (Plugin manifests are hand-maintained SSOT under packages/*-plugin/;
252
+ // wp compile no longer emits them — golden tests + version stamping own
253
+ // that surface.)
266
254
  const agentsLayers = collectAgentsLayers(agentDir);
267
255
  if (agentsLayers.length > 0) {
268
256
  const { mergeAgentsMd } = await import("#compiler/memory/merger");
@@ -278,11 +266,9 @@ export async function runCompile(options) {
278
266
  writeFileSync(join(cwd, "AGENTS.md"), mergeResult.content);
279
267
  }
280
268
  }
281
- // Step 6: Write .compile-manifest.json with content-hash sentinels
269
+ // Step 5: Write .compile-manifest.json with content-hash sentinels
282
270
  const outputHashes = {
283
271
  "AGENTS.md": hashFile(join(cwd, "AGENTS.md")),
284
- "packages/claude-plugin/.claude-plugin/plugin.json": hashFile(join(cwd, "packages", "claude-plugin", ".claude-plugin", "plugin.json")),
285
- "packages/codex-plugin/.codex-plugin/plugin.json": hashFile(join(cwd, "packages", "codex-plugin", ".codex-plugin", "plugin.json")),
286
272
  };
287
273
  const compileManifest = {
288
274
  version: COMPILE_MANIFEST_VERSION,
@@ -303,18 +289,6 @@ export async function runCompile(options) {
303
289
  cleanup();
304
290
  }
305
291
  }
306
- function readPackageVersion(cwd) {
307
- const pkgPath = join(cwd, "package.json");
308
- if (!existsSync(pkgPath))
309
- return "0.0.0";
310
- try {
311
- const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
312
- return typeof parsed.version === "string" ? parsed.version : "0.0.0";
313
- }
314
- catch {
315
- return "0.0.0";
316
- }
317
- }
318
292
  function collectAgentsLayers(agentDir) {
319
293
  const agentsDir = join(agentDir, "agents");
320
294
  if (!existsSync(agentsDir))
@@ -56,3 +56,4 @@ export type DeliveryStatusOptions = {
56
56
  * prior values and expose a retryable adapter warning.
57
57
  */
58
58
  export declare function collectDeliveryStatus(options?: DeliveryStatusOptions): Promise<DeliveryStatus>;
59
+ export declare function blueprintDocumentPath(cwd: string, state: string, slug: string): string | null;
@@ -107,14 +107,21 @@ function checkState(value) {
107
107
  return "pending";
108
108
  return states.length > 0 ? "passing" : "unknown";
109
109
  }
110
+ export function blueprintDocumentPath(cwd, state, slug) {
111
+ const folder = join(cwd, "blueprints", state, slug, "_overview.md");
112
+ if (existsSync(folder))
113
+ return folder;
114
+ const flat = join(cwd, "blueprints", state, `${slug}.md`);
115
+ return existsSync(flat) ? flat : null;
116
+ }
110
117
  function readBlueprintDelivery(cwd) {
111
118
  const branch = run("git", ["branch", "--show-current"], cwd);
112
119
  if (!branch?.startsWith("bp/"))
113
120
  return null;
114
121
  const slug = branch.slice(3);
115
122
  for (const state of ["in-progress", "planned", "draft", "completed", "parked", "archived"]) {
116
- const path = join(cwd, "blueprints", state, slug, "_overview.md");
117
- if (!existsSync(path))
123
+ const path = blueprintDocumentPath(cwd, state, slug);
124
+ if (!path)
118
125
  continue;
119
126
  const raw = readFileSync(path, "utf8");
120
127
  return {
@@ -129,8 +136,8 @@ function readVerificationDelivery(cwd) {
129
136
  const blueprint = readBlueprintDelivery(cwd);
130
137
  if (!blueprint)
131
138
  return null;
132
- const path = join(cwd, "blueprints", blueprint.state, blueprint.slug, "_overview.md");
133
- if (!existsSync(path))
139
+ const path = blueprintDocumentPath(cwd, blueprint.state, blueprint.slug);
140
+ if (!path)
134
141
  return null;
135
142
  const raw = readFileSync(path, "utf8");
136
143
  const evidenceCount = (raw.match(/^\*\*Verification:\*\*/gmu) ?? []).length;
@@ -27,6 +27,7 @@ const MENU_BINDINGS = [
27
27
  { keys: "x", label: "actions", footerPriority: 0 },
28
28
  { keys: "⇥/^s", label: "sessions" },
29
29
  { keys: "b", label: "blueprints" },
30
+ { keys: "c", label: "chat" },
30
31
  { keys: "d/^o", label: "mode" },
31
32
  { keys: "^p", label: "palette", footerPriority: 1 },
32
33
  { keys: "?/^x", label: "help" },
@@ -1,7 +1,9 @@
1
1
  import { type DashPermissionMode, type DashProvider } from "#cli/commands/dash/provider-capabilities.js";
2
+ import { type ControlPlaneChatMode } from "#cli/commands/dash/tui/chat-mode.js";
2
3
  export type LaunchPreferences = {
3
4
  readonly version: number;
4
5
  readonly yolo: boolean;
6
+ readonly chatMode?: ControlPlaneChatMode;
5
7
  };
6
8
  export type YoloPreferenceState = {
7
9
  readonly enabled: boolean;
@@ -9,9 +11,22 @@ export type YoloPreferenceState = {
9
11
  /** The preference file exists but could not be parsed (fails safe to off). */
10
12
  readonly corrupt: boolean;
11
13
  };
14
+ export type ChatModePreferenceState = {
15
+ readonly mode: ControlPlaneChatMode;
16
+ readonly persisted: boolean;
17
+ /** The preference file exists but could not be parsed (fails safe to host-pane). */
18
+ readonly corrupt: boolean;
19
+ };
12
20
  export declare function readYoloPreferenceState(): YoloPreferenceState;
13
21
  export declare function readYoloPreference(): boolean;
14
22
  export declare function writeYoloPreference(enabled: boolean): void;
23
+ /**
24
+ * Read persisted control-plane chat mode. Missing, corrupt, or invalid values
25
+ * fail safe to host-pane (never open integrated-chat chrome from bad prefs).
26
+ */
27
+ export declare function readChatModePreferenceState(): ChatModePreferenceState;
28
+ export declare function readChatModePreference(): ControlPlaneChatMode;
29
+ export declare function writeChatModePreference(mode: ControlPlaneChatMode): void;
15
30
  /**
16
31
  * Precedence: explicit `--wp-permission-mode` > provider-native permission flags
17
32
  * already in args > persisted yolo (true → 'dangerous') > 'safe'. When `provider`
@@ -1,12 +1,12 @@
1
- // User-scope persistence for the personal "yolo" launch preference. This is a
2
- // per-user risk choice (like ~/.codex/config.toml), deliberately NOT the tracked
3
- // .webpressorc.json a repo file would let one contributor flip teammates into
4
- // bypass via git pull. The file lives under the env-paths state root and any
5
- // corrupt/missing read fails safe (yolo off).
1
+ // User-scope persistence for personal dash launch preferences (yolo + chat mode).
2
+ // Deliberately NOT the tracked .webpressorc.json a repo file would let one
3
+ // contributor flip teammates into bypass via git pull. The file lives under the
4
+ // env-paths state root; corrupt/missing reads fail safe (yolo off, host-pane).
6
5
  import { randomUUID } from "node:crypto";
7
6
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
8
7
  import { dirname, join } from "node:path";
9
8
  import { hasProviderNativePermissionFlag, parseDashPermissionMode, } from "#cli/commands/dash/provider-capabilities.js";
9
+ import { DEFAULT_CONTROL_PLANE_CHAT_MODE, isControlPlaneChatMode, } from "#cli/commands/dash/tui/chat-mode.js";
10
10
  import { getStateRoot } from "#paths/state-root.js";
11
11
  const LAUNCH_PREFERENCES_FILENAME = "launch-preferences.json";
12
12
  const LAUNCH_PREFERENCES_VERSION = 1;
@@ -31,6 +31,23 @@ function readRawPreferences() {
31
31
  return null;
32
32
  }
33
33
  }
34
+ /** Atomic read-merge-write so unknown fields survive partial updates. */
35
+ function writePreferencesPatch(patch) {
36
+ const path = launchPreferencesPath();
37
+ mkdirSync(dirname(path), { recursive: true });
38
+ // A corrupt existing file is discarded (readRawPreferences → null).
39
+ const existing = readRawPreferences() ?? {};
40
+ const payload = { ...existing, version: LAUNCH_PREFERENCES_VERSION, ...patch };
41
+ const tmpPath = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
42
+ try {
43
+ writeFileSync(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
44
+ renameSync(tmpPath, path);
45
+ }
46
+ catch (error) {
47
+ rmSync(tmpPath, { force: true });
48
+ throw error;
49
+ }
50
+ }
34
51
  export function readYoloPreferenceState() {
35
52
  const path = launchPreferencesPath();
36
53
  if (!existsSync(path))
@@ -44,21 +61,33 @@ export function readYoloPreference() {
44
61
  return readYoloPreferenceState().enabled;
45
62
  }
46
63
  export function writeYoloPreference(enabled) {
64
+ writePreferencesPatch({ yolo: enabled });
65
+ }
66
+ /**
67
+ * Read persisted control-plane chat mode. Missing, corrupt, or invalid values
68
+ * fail safe to host-pane (never open integrated-chat chrome from bad prefs).
69
+ */
70
+ export function readChatModePreferenceState() {
47
71
  const path = launchPreferencesPath();
48
- mkdirSync(dirname(path), { recursive: true });
49
- // Read-merge-write so unknown fields written by a newer wp survive an older
50
- // wp's toggle. A corrupt existing file is discarded (readRawPreferences → null).
51
- const existing = readRawPreferences() ?? {};
52
- const payload = { ...existing, version: LAUNCH_PREFERENCES_VERSION, yolo: enabled };
53
- const tmpPath = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
54
- try {
55
- writeFileSync(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
56
- renameSync(tmpPath, path);
72
+ if (!existsSync(path)) {
73
+ return { mode: DEFAULT_CONTROL_PLANE_CHAT_MODE, persisted: false, corrupt: false };
57
74
  }
58
- catch (error) {
59
- rmSync(tmpPath, { force: true });
60
- throw error;
75
+ const raw = readRawPreferences();
76
+ if (raw === null) {
77
+ return { mode: DEFAULT_CONTROL_PLANE_CHAT_MODE, persisted: false, corrupt: true };
61
78
  }
79
+ const value = raw.chatMode;
80
+ if (typeof value === "string" && isControlPlaneChatMode(value)) {
81
+ return { mode: value, persisted: true, corrupt: false };
82
+ }
83
+ // File present but chatMode absent or invalid — not corrupt JSON.
84
+ return { mode: DEFAULT_CONTROL_PLANE_CHAT_MODE, persisted: true, corrupt: false };
85
+ }
86
+ export function readChatModePreference() {
87
+ return readChatModePreferenceState().mode;
88
+ }
89
+ export function writeChatModePreference(mode) {
90
+ writePreferencesPatch({ chatMode: mode });
62
91
  }
63
92
  /**
64
93
  * Precedence: explicit `--wp-permission-mode` > provider-native permission flags
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Shared plan-once run label contract.
3
+ * Exact equality is SSOT for membership ↔ registry join; keep one helper.
4
+ */
5
+ import type { PlanOnceHostEntry } from "#cli/commands/dash/plan-once-schema.js";
6
+ /** Durable run label: `plan-once:<planId>:<label|host>`. */
7
+ export declare function planOnceRunLabel(planId: string, host: PlanOnceHostEntry): string;
@@ -0,0 +1,5 @@
1
+ /** Durable run label: `plan-once:<planId>:<label|host>`. */
2
+ export function planOnceRunLabel(planId, host) {
3
+ const base = host.label?.trim() || host.host;
4
+ return `plan-once:${planId}:${base}`;
5
+ }
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+ import { type MultiHostPlanProposal } from "#cli/commands/dash/plan-once-schema.js";
3
+ export declare const PLAN_MEMBERSHIP_SCHEMA_VERSION: 1;
4
+ export declare const planMembershipHostSchema: z.ZodObject<{
5
+ host: z.ZodEnum<{
6
+ claude: "claude";
7
+ codex: "codex";
8
+ grok: "grok";
9
+ opencode: "opencode";
10
+ }>;
11
+ workdir: z.ZodString;
12
+ label: z.ZodString;
13
+ }, z.core.$strict>;
14
+ export type PlanMembershipHost = z.infer<typeof planMembershipHostSchema>;
15
+ export declare const planMembershipSchema: z.ZodObject<{
16
+ schemaVersion: z.ZodLiteral<1>;
17
+ planId: z.ZodString;
18
+ hosts: z.ZodArray<z.ZodObject<{
19
+ host: z.ZodEnum<{
20
+ claude: "claude";
21
+ codex: "codex";
22
+ grok: "grok";
23
+ opencode: "opencode";
24
+ }>;
25
+ workdir: z.ZodString;
26
+ label: z.ZodString;
27
+ }, z.core.$strict>>;
28
+ confirmedAt: z.ZodString;
29
+ }, z.core.$strict>;
30
+ export type PlanMembership = z.infer<typeof planMembershipSchema>;
31
+ export declare const planMembershipStoreSchema: z.ZodObject<{
32
+ version: z.ZodLiteral<1>;
33
+ plans: z.ZodRecord<z.ZodString, z.ZodObject<{
34
+ schemaVersion: z.ZodLiteral<1>;
35
+ planId: z.ZodString;
36
+ hosts: z.ZodArray<z.ZodObject<{
37
+ host: z.ZodEnum<{
38
+ claude: "claude";
39
+ codex: "codex";
40
+ grok: "grok";
41
+ opencode: "opencode";
42
+ }>;
43
+ workdir: z.ZodString;
44
+ label: z.ZodString;
45
+ }, z.core.$strict>>;
46
+ confirmedAt: z.ZodString;
47
+ }, z.core.$strict>>;
48
+ }, z.core.$strict>;
49
+ export type PlanMembershipStore = z.infer<typeof planMembershipStoreSchema>;
50
+ /** Pure: build immutable membership from a confirmed multi-host plan. */
51
+ export declare function buildPlanMembership(plan: MultiHostPlanProposal, confirmedAt?: string): PlanMembership;
52
+ /** Persist membership for a planId (idempotent overwrite for same planId). */
53
+ export declare function writePlanMembership(membership: PlanMembership, options?: {
54
+ readonly cwd?: string;
55
+ }): Promise<PlanMembership>;
56
+ /** Read membership for planId, or null if absent. */
57
+ export declare function readPlanMembership(planId: string, options?: {
58
+ readonly cwd?: string;
59
+ }): Promise<PlanMembership | null>;
60
+ /** List all durable plan memberships (operator / merge projection scan). */
61
+ export declare function listPlanMemberships(options?: {
62
+ readonly cwd?: string;
63
+ }): Promise<readonly PlanMembership[]>;
64
+ /** Test helper: clear membership store file content via rewrite empty. */
65
+ export declare function clearPlanMembershipStoreForTests(options: {
66
+ readonly cwd?: string;
67
+ }): Promise<void>;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Durable plan-once membership (expected host set for a planId).
3
+ *
4
+ * Labels alone are not N-of-N SSOT: a host that never creates a run disappears
5
+ * from registry-only views. Membership is written on confirm (not cancel) and
6
+ * is a plan-record sidecar — not a second run-registry.
7
+ */
8
+ import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
9
+ import { randomUUID } from "node:crypto";
10
+ import { dirname } from "node:path";
11
+ import { z } from "zod";
12
+ import { planOnceRunLabel } from "#cli/commands/dash/plan-once-label.js";
13
+ import { planOnceHostSchema, } from "#cli/commands/dash/plan-once-schema.js";
14
+ import { getSurfacePathAsync, withLock } from "#paths/state-root.js";
15
+ import { writeJsonFileAtomic } from "#shared-utils/write-json-file.js";
16
+ export const PLAN_MEMBERSHIP_SCHEMA_VERSION = 1;
17
+ const MEMBERSHIP_SURFACE = "dash/plan-once-memberships.json";
18
+ export const planMembershipHostSchema = z
19
+ .object({
20
+ host: planOnceHostSchema,
21
+ workdir: z.string().min(1),
22
+ /** Expected run label: plan-once:<planId>:<label|host> */
23
+ label: z.string().min(1),
24
+ })
25
+ .strict();
26
+ export const planMembershipSchema = z
27
+ .object({
28
+ schemaVersion: z.literal(PLAN_MEMBERSHIP_SCHEMA_VERSION),
29
+ planId: z.string().uuid(),
30
+ hosts: z.array(planMembershipHostSchema).min(1),
31
+ confirmedAt: z.string().datetime(),
32
+ })
33
+ .strict();
34
+ export const planMembershipStoreSchema = z
35
+ .object({
36
+ version: z.literal(1),
37
+ plans: z.record(z.string(), planMembershipSchema),
38
+ })
39
+ .strict();
40
+ function emptyStore() {
41
+ return { version: 1, plans: {} };
42
+ }
43
+ /** Pure: build immutable membership from a confirmed multi-host plan. */
44
+ export function buildPlanMembership(plan, confirmedAt = new Date().toISOString()) {
45
+ const hosts = plan.hosts.map((entry) => ({
46
+ host: entry.host,
47
+ workdir: entry.workdir,
48
+ label: planOnceRunLabel(plan.planId, entry),
49
+ }));
50
+ return planMembershipSchema.parse({
51
+ schemaVersion: PLAN_MEMBERSHIP_SCHEMA_VERSION,
52
+ planId: plan.planId,
53
+ hosts,
54
+ confirmedAt,
55
+ });
56
+ }
57
+ async function membershipPath(cwd) {
58
+ return getSurfacePathAsync(MEMBERSHIP_SURFACE, "repo", cwd);
59
+ }
60
+ function corruptCopyPath(path) {
61
+ return `${path}.corrupt-${new Date().toISOString().replaceAll(":", "-")}-${randomUUID()}`;
62
+ }
63
+ function preserveStore(path) {
64
+ if (!existsSync(path))
65
+ return undefined;
66
+ const preservedPath = corruptCopyPath(path);
67
+ try {
68
+ copyFileSync(path, preservedPath);
69
+ return preservedPath;
70
+ }
71
+ catch {
72
+ return undefined;
73
+ }
74
+ }
75
+ /**
76
+ * Read membership store. Missing file → empty (ok).
77
+ * Corrupt/unreadable file → ok:false with quarantine path; never treat as empty
78
+ * for write merge (would wipe sibling plan memberships).
79
+ */
80
+ function readStoreFromPath(path) {
81
+ if (!existsSync(path))
82
+ return { ok: true, store: emptyStore() };
83
+ try {
84
+ const raw = JSON.parse(readFileSync(path, "utf8"));
85
+ const parsed = planMembershipStoreSchema.safeParse(raw);
86
+ if (parsed.success)
87
+ return { ok: true, store: parsed.data };
88
+ const preservedPath = preserveStore(path);
89
+ return {
90
+ ok: false,
91
+ reason: "plan membership store failed schema validation",
92
+ ...(preservedPath ? { preservedPath } : {}),
93
+ };
94
+ }
95
+ catch (error) {
96
+ const preservedPath = preserveStore(path);
97
+ return {
98
+ ok: false,
99
+ reason: error instanceof Error ? error.message : "plan membership store unreadable",
100
+ ...(preservedPath ? { preservedPath } : {}),
101
+ };
102
+ }
103
+ }
104
+ function storeForRead(path) {
105
+ const read = readStoreFromPath(path);
106
+ // Projection reads fail closed to empty (no memberships) without wiping disk.
107
+ return read.ok ? read.store : emptyStore();
108
+ }
109
+ /** Persist membership for a planId (idempotent overwrite for same planId). */
110
+ export async function writePlanMembership(membership, options = {}) {
111
+ const validated = planMembershipSchema.parse(membership);
112
+ const path = await membershipPath(options.cwd);
113
+ return withLock("repo", () => {
114
+ const read = readStoreFromPath(path);
115
+ if (!read.ok) {
116
+ const preserved = read.preservedPath ? ` (preserved ${read.preservedPath})` : "";
117
+ throw new Error(`Refusing to write plan membership: store corrupt — ${read.reason}${preserved}`);
118
+ }
119
+ const next = {
120
+ version: 1,
121
+ plans: { ...read.store.plans, [validated.planId]: validated },
122
+ };
123
+ mkdirSync(dirname(path), { recursive: true });
124
+ writeJsonFileAtomic(path, planMembershipStoreSchema.parse(next));
125
+ return validated;
126
+ }, options.cwd);
127
+ }
128
+ /** Read membership for planId, or null if absent. */
129
+ export async function readPlanMembership(planId, options = {}) {
130
+ const path = await membershipPath(options.cwd);
131
+ return withLock("repo", () => {
132
+ const store = storeForRead(path);
133
+ return store.plans[planId] ?? null;
134
+ }, options.cwd);
135
+ }
136
+ /** List all durable plan memberships (operator / merge projection scan). */
137
+ export async function listPlanMemberships(options = {}) {
138
+ const path = await membershipPath(options.cwd);
139
+ return withLock("repo", () => {
140
+ const store = storeForRead(path);
141
+ return Object.values(store.plans);
142
+ }, options.cwd);
143
+ }
144
+ /** Test helper: clear membership store file content via rewrite empty. */
145
+ export async function clearPlanMembershipStoreForTests(options) {
146
+ const path = await membershipPath(options.cwd);
147
+ await withLock("repo", () => {
148
+ mkdirSync(dirname(path), { recursive: true });
149
+ writeJsonFileAtomic(path, emptyStore());
150
+ }, options.cwd);
151
+ }