@webpresso/agent-kit 3.1.30 → 3.3.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 (300) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +183 -20
  3. package/catalog/AGENTS.md.tpl +3 -3
  4. package/catalog/agent/agents/implementer.md +45 -0
  5. package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
  6. package/catalog/agent/rules/pre-implementation.md +28 -1
  7. package/catalog/agent/rules/supported-agent-clis.md +9 -0
  8. package/catalog/agent/rules/typescript-version.md +50 -0
  9. package/catalog/agent/skills/codex/SKILL.md +25 -1
  10. package/catalog/agent/skills/verify/SKILL.md +4 -2
  11. package/catalog/docs/templates/blueprint.md +1 -1
  12. package/dist/esm/audit/agents.js +1 -0
  13. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
  14. package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
  15. package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
  16. package/dist/esm/audit/blueprint-trust.js +14 -10
  17. package/dist/esm/audit/changeset-evidence.d.ts +23 -0
  18. package/dist/esm/audit/changeset-evidence.js +30 -0
  19. package/dist/esm/audit/changeset-required.d.ts +10 -0
  20. package/dist/esm/audit/changeset-required.js +73 -0
  21. package/dist/esm/audit/ci-guardrails-detection.d.ts +27 -0
  22. package/dist/esm/audit/ci-guardrails-detection.js +68 -0
  23. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  24. package/dist/esm/audit/ci-guardrails-wiring.js +80 -0
  25. package/dist/esm/audit/ci-test-perf.js +30 -0
  26. package/dist/esm/audit/command-surface.js +4 -2
  27. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  28. package/dist/esm/audit/host-substitution-risk.js +256 -0
  29. package/dist/esm/audit/registry.d.ts +4 -4
  30. package/dist/esm/audit/registry.js +13 -1
  31. package/dist/esm/audit/toolchain-isolation.js +1 -2
  32. package/dist/esm/audit/typescript-version.d.ts +2 -0
  33. package/dist/esm/audit/typescript-version.js +123 -0
  34. package/dist/esm/blueprint/core/parser.js +101 -2
  35. package/dist/esm/blueprint/core/schema.d.ts +1 -0
  36. package/dist/esm/blueprint/core/schema.js +26 -0
  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 +87 -20
  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/local.d.ts +2 -1
  46. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  47. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  48. package/dist/esm/blueprint/lifecycle/review-provenance.js +68 -12
  49. package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +97 -0
  50. package/dist/esm/blueprint/lifecycle/rollout-anchor.js +161 -0
  51. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  52. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  53. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  54. package/dist/esm/blueprint/sync/client.d.ts +2 -1
  55. package/dist/esm/blueprint/sync/client.js +1 -1
  56. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  57. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  58. package/dist/esm/blueprint/trust/dossier.d.ts +1 -0
  59. package/dist/esm/blueprint/trust/dossier.js +40 -6
  60. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  61. package/dist/esm/blueprint/trust/gates.js +20 -0
  62. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  63. package/dist/esm/blueprint/trust/promotion.js +74 -112
  64. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  65. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  66. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  67. package/dist/esm/blueprint/trust/validator.js +7 -1
  68. package/dist/esm/blueprint/utils/archive.js +27 -5
  69. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  70. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  71. package/dist/esm/build/cli-mcp-parity.js +15 -0
  72. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  73. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  74. package/dist/esm/cli/cli.d.ts +1 -1
  75. package/dist/esm/cli/cli.js +43 -0
  76. package/dist/esm/cli/commands/agent-launch.d.ts +8 -1
  77. package/dist/esm/cli/commands/agent-launch.js +93 -4
  78. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  79. package/dist/esm/cli/commands/audit-core.js +8 -1
  80. package/dist/esm/cli/commands/audit.js +21 -5
  81. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  82. package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
  83. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  84. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  85. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  86. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  87. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  88. package/dist/esm/cli/commands/blueprint/router.js +13 -1
  89. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  90. package/dist/esm/cli/commands/ci-preflight.js +30 -6
  91. package/dist/esm/cli/commands/compile.js +5 -31
  92. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  93. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  94. package/dist/esm/cli/commands/doctor.js +5 -0
  95. package/dist/esm/cli/commands/hook.d.ts +5 -0
  96. package/dist/esm/cli/commands/hook.js +15 -9
  97. package/dist/esm/cli/commands/init/config.js +2 -2
  98. package/dist/esm/cli/commands/init/convergence-apply.d.ts +88 -0
  99. package/dist/esm/cli/commands/init/convergence-apply.js +325 -0
  100. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  101. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  102. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  103. package/dist/esm/cli/commands/init/convergence.js +57 -0
  104. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  105. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  106. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  107. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  108. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  109. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  110. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  111. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  112. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  113. package/dist/esm/cli/commands/init/index.d.ts +3 -0
  114. package/dist/esm/cli/commands/init/index.js +326 -173
  115. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  116. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  117. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  118. package/dist/esm/cli/commands/init/merge.js +30 -1
  119. package/dist/esm/cli/commands/init/package-root.d.ts +7 -0
  120. package/dist/esm/cli/commands/init/package-root.js +10 -2
  121. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  122. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  123. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  124. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  125. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +20 -0
  126. package/dist/esm/cli/commands/init/scaffold-base-kit.js +61 -5
  127. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  128. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  129. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  130. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +54 -24
  131. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  132. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
  133. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +22 -0
  134. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +230 -0
  135. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  136. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  137. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  138. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  139. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  140. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  141. package/dist/esm/cli/commands/opencode-probe.d.ts +107 -0
  142. package/dist/esm/cli/commands/opencode-probe.js +323 -0
  143. package/dist/esm/cli/commands/opencode-rotate.d.ts +72 -0
  144. package/dist/esm/cli/commands/opencode-rotate.js +147 -0
  145. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  146. package/dist/esm/cli/commands/public-readiness.js +42 -0
  147. package/dist/esm/cli/commands/quality-runner.js +3 -3
  148. package/dist/esm/cli/commands/review.d.ts +35 -3
  149. package/dist/esm/cli/commands/review.js +751 -209
  150. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  151. package/dist/esm/cli/commands/run-wait.js +122 -0
  152. package/dist/esm/cli/commands/rust-check.d.ts +24 -0
  153. package/dist/esm/cli/commands/rust-check.js +110 -0
  154. package/dist/esm/cli/commands/setup-help.js +1 -1
  155. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  156. package/dist/esm/cli/commands/typecheck.d.ts +1 -0
  157. package/dist/esm/cli/commands/typecheck.js +30 -1
  158. package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +6 -0
  159. package/dist/esm/cli/commands/worktree/router-dispatch.js +126 -9
  160. package/dist/esm/cli/commands/worktree/router.js +10 -0
  161. package/dist/esm/cli/direct-provider-launch.js +9 -5
  162. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  163. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  164. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  165. package/dist/esm/daemon/domains/git-actions.js +136 -0
  166. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  167. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  168. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  169. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  170. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  171. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  172. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  173. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  174. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  175. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  176. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  177. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  178. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  179. package/dist/esm/daemon/handlers/product.js +10 -119
  180. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  181. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  182. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  183. package/dist/esm/daemon/handlers/shared.js +16 -0
  184. package/dist/esm/daemon/methods.d.ts +8 -1
  185. package/dist/esm/daemon/methods.js +7 -0
  186. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  187. package/dist/esm/daemon/protocol/contract.js +26 -0
  188. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  189. package/dist/esm/daemon/protocol/index.js +1 -1
  190. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  191. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  192. package/dist/esm/hooks/conformance/batch.js +5 -0
  193. package/dist/esm/hooks/doctor.d.ts +5 -3
  194. package/dist/esm/hooks/doctor.js +67 -16
  195. package/dist/esm/hooks/permission-request/index.js +9 -3
  196. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  197. package/dist/esm/hooks/permission-request/policy.js +13 -0
  198. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  199. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  200. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  201. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  202. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  203. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  204. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  205. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
  206. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  207. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  208. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  209. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  210. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +6 -1
  211. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  212. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  213. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  214. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  215. package/dist/esm/hooks/shared/types.d.ts +8 -5
  216. package/dist/esm/hooks/shared/types.js +50 -9
  217. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  218. package/dist/esm/hooks/stop/qa-changed-files.js +92 -0
  219. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  220. package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
  221. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +109 -11
  222. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  223. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  224. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  225. package/dist/esm/mcp/blueprint/registration.js +3 -2
  226. package/dist/esm/mcp/server.d.ts +1 -0
  227. package/dist/esm/mcp/server.js +27 -0
  228. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  229. package/dist/esm/mcp/tools/_names.js +5 -0
  230. package/dist/esm/mcp/tools/_registry.js +10 -0
  231. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  232. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  233. package/dist/esm/mcp/tools/audit.d.ts +5 -1
  234. package/dist/esm/mcp/tools/audit.js +42 -0
  235. package/dist/esm/mcp/tools/audits.d.ts +4 -0
  236. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  237. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  238. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  239. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  240. package/dist/esm/mcp/tools/review-run.js +216 -0
  241. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  242. package/dist/esm/mcp/tools/run-wait.js +593 -0
  243. package/dist/esm/mcp/tools/session-fetch-and-index.d.ts +1 -1
  244. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  245. package/dist/esm/mcp/tools/session-id.js +45 -0
  246. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  247. package/dist/esm/mcp/tools/session-info.js +49 -0
  248. package/dist/esm/output-transforms/audit.d.ts +23 -0
  249. package/dist/esm/output-transforms/audit.js +47 -0
  250. package/dist/esm/output-transforms/index.js +2 -0
  251. package/dist/esm/package.json +2 -0
  252. package/dist/esm/platform/client.d.ts +7 -1
  253. package/dist/esm/platform/github-contract-source.d.ts +2 -1
  254. package/dist/esm/pr-description/contract.js +24 -4
  255. package/dist/esm/review/authority.js +27 -14
  256. package/dist/esm/review/availability.d.ts +33 -0
  257. package/dist/esm/review/availability.js +122 -20
  258. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  259. package/dist/esm/review/delivery-verifier.js +115 -0
  260. package/dist/esm/review/events.d.ts +3 -2
  261. package/dist/esm/review/events.js +23 -3
  262. package/dist/esm/review/execution/adapters.d.ts +11 -1
  263. package/dist/esm/review/execution/adapters.js +36 -10
  264. package/dist/esm/review/execution/artifacts.d.ts +1 -1
  265. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  266. package/dist/esm/review/execution/output-policy.js +12 -0
  267. package/dist/esm/review/execution/review-checkout.js +121 -21
  268. package/dist/esm/review/execution/supervisor.js +36 -4
  269. package/dist/esm/review/execution/types.d.ts +9 -0
  270. package/dist/esm/review/opencode-account.d.ts +24 -0
  271. package/dist/esm/review/opencode-account.js +68 -0
  272. package/dist/esm/review/opencode-auth.d.ts +33 -0
  273. package/dist/esm/review/opencode-auth.js +79 -0
  274. package/dist/esm/review/subject.d.ts +25 -0
  275. package/dist/esm/review/subject.js +116 -26
  276. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  277. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  278. package/dist/esm/rust/affected-manifests.d.ts +38 -0
  279. package/dist/esm/rust/affected-manifests.js +69 -0
  280. package/dist/esm/session-memory/fetch-index.d.ts +1 -1
  281. package/dist/esm/status/snapshot.js +32 -25
  282. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  283. package/dist/esm/test/duration-sequencer.js +54 -0
  284. package/dist/esm/test/shard-durations.json +922 -0
  285. package/dist/esm/test/worker-budget.d.ts +6 -0
  286. package/dist/esm/test/worker-budget.js +59 -1
  287. package/dist/esm/test-helpers/global-setup.d.ts +1 -2
  288. package/dist/esm/test-helpers/global-setup.js +79 -32
  289. package/dist/esm/typecheck/planner.d.ts +9 -1
  290. package/dist/esm/typecheck/planner.js +31 -0
  291. package/dist/esm/ultragoal/service.d.ts +7 -0
  292. package/dist/esm/ultragoal/service.js +42 -2
  293. package/dist/esm/utils/write-json-file.js +3 -1
  294. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  295. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  296. package/dist/esm/worktrees/orphan-scan.d.ts +50 -0
  297. package/dist/esm/worktrees/orphan-scan.js +211 -0
  298. package/dist/esm/worktrees/registry.d.ts +8 -0
  299. package/dist/esm/worktrees/registry.js +18 -3
  300. package/package.json +18 -13
@@ -0,0 +1,2 @@
1
+ export declare const RUN_WAIT_HELP = "wp run wait \u2014 read-only GitHub workflow-run waiter (push/dispatch runs, e.g. production deploys on main)\n\nUsage:\n wp run wait --run-id <id> [options]\n wp run wait --sha <head-sha> [--workflow <file-or-name>] [options]\n\nSelector (exactly one required):\n --run-id <id> Watch a specific workflow run by database id\n --sha <sha> Watch the newest run for a head commit SHA\n --workflow <file|name> Filter --sha selection to one workflow\n\nOptions:\n --until <target> run-terminal (default) | all-jobs-terminal\n --poll-interval-ms <n> Poll interval (default 15000)\n --timeout-ms <n> Overall watch deadline (max 300000, default 120000)\n --max-output-bytes <n> Per-command output budget\n --cwd <path> Project root or descendant\n --json Emit exactly one JSON document (default output)\n -h, --help Show this help\n\nRead-only: issues only `gh` GET calls; never mutates run state.";
2
+ export declare function runRunWaitCommand(args: readonly string[]): Promise<number>;
@@ -0,0 +1,122 @@
1
+ import { RUN_WAIT_UNTIL_VALUES, runRunWait, } from "#mcp/tools/run-wait.js";
2
+ // `wp run wait` is intercepted in cli.ts before the package-manager `run`
3
+ // facade (which maps `wp run <script>` to `vp run <script>`). This module owns
4
+ // only the `wait` subcommand and keeps arg parsing dependency-free so the
5
+ // interception path stays cheap.
6
+ export const RUN_WAIT_HELP = `wp run wait — read-only GitHub workflow-run waiter (push/dispatch runs, e.g. production deploys on main)
7
+
8
+ Usage:
9
+ wp run wait --run-id <id> [options]
10
+ wp run wait --sha <head-sha> [--workflow <file-or-name>] [options]
11
+
12
+ Selector (exactly one required):
13
+ --run-id <id> Watch a specific workflow run by database id
14
+ --sha <sha> Watch the newest run for a head commit SHA
15
+ --workflow <file|name> Filter --sha selection to one workflow
16
+
17
+ Options:
18
+ --until <target> run-terminal (default) | all-jobs-terminal
19
+ --poll-interval-ms <n> Poll interval (default 15000)
20
+ --timeout-ms <n> Overall watch deadline (max 300000, default 120000)
21
+ --max-output-bytes <n> Per-command output budget
22
+ --cwd <path> Project root or descendant
23
+ --json Emit exactly one JSON document (default output)
24
+ -h, --help Show this help
25
+
26
+ Read-only: issues only \`gh\` GET calls; never mutates run state.`;
27
+ function parsePositiveInteger(value, name) {
28
+ if (value === undefined)
29
+ return undefined;
30
+ const parsed = Number.parseInt(value, 10);
31
+ if (!Number.isFinite(parsed) || parsed <= 0) {
32
+ throw new Error(`${name} must be a positive integer`);
33
+ }
34
+ return parsed;
35
+ }
36
+ function normalizeUntil(value) {
37
+ if (value === undefined)
38
+ return undefined;
39
+ const normalized = value.replaceAll("-", "_");
40
+ if (!RUN_WAIT_UNTIL_VALUES.includes(normalized)) {
41
+ throw new Error(`Unknown run wait target: ${value}`);
42
+ }
43
+ return normalized;
44
+ }
45
+ const VALUE_FLAGS = new Set([
46
+ "--run-id",
47
+ "--sha",
48
+ "--workflow",
49
+ "--until",
50
+ "--poll-interval-ms",
51
+ "--timeout-ms",
52
+ "--max-output-bytes",
53
+ "--cwd",
54
+ ]);
55
+ function parseFlags(args) {
56
+ const values = {};
57
+ let help = false;
58
+ for (let index = 0; index < args.length; index += 1) {
59
+ const arg = args[index];
60
+ if (arg === undefined)
61
+ continue;
62
+ if (arg === "--help" || arg === "-h") {
63
+ help = true;
64
+ continue;
65
+ }
66
+ if (arg === "--json")
67
+ continue; // JSON is the only output shape
68
+ const eq = arg.indexOf("=");
69
+ if (eq !== -1 && arg.startsWith("--")) {
70
+ const key = arg.slice(0, eq);
71
+ if (VALUE_FLAGS.has(key)) {
72
+ values[key] = arg.slice(eq + 1);
73
+ continue;
74
+ }
75
+ }
76
+ if (VALUE_FLAGS.has(arg)) {
77
+ const next = args[index + 1];
78
+ if (next === undefined)
79
+ throw new Error(`${arg} requires a value`);
80
+ values[arg] = next;
81
+ index += 1;
82
+ continue;
83
+ }
84
+ throw new Error(`Unknown run wait option: ${arg}`);
85
+ }
86
+ return { values, help };
87
+ }
88
+ export async function runRunWaitCommand(args) {
89
+ const { values, help } = parseFlags(args);
90
+ if (help) {
91
+ console.log(RUN_WAIT_HELP);
92
+ return 0;
93
+ }
94
+ const runId = values["--run-id"];
95
+ const input = {
96
+ sha: values["--sha"],
97
+ runId: runId === undefined ? undefined : runId,
98
+ workflow: values["--workflow"],
99
+ until: normalizeUntil(values["--until"]),
100
+ pollIntervalMs: parsePositiveInteger(values["--poll-interval-ms"], "--poll-interval-ms"),
101
+ timeoutMs: parsePositiveInteger(values["--timeout-ms"], "--timeout-ms"),
102
+ maxOutputBytes: parsePositiveInteger(values["--max-output-bytes"], "--max-output-bytes"),
103
+ cwd: values["--cwd"],
104
+ };
105
+ let payload;
106
+ try {
107
+ payload = await runRunWait(input);
108
+ }
109
+ catch (error) {
110
+ const issues = error.issues;
111
+ if (Array.isArray(issues)) {
112
+ const message = issues
113
+ .map((issue) => (typeof issue.message === "string" ? issue.message : String(issue.message)))
114
+ .join("; ");
115
+ console.error(`wp run wait: ${message}`);
116
+ return 1;
117
+ }
118
+ throw error;
119
+ }
120
+ console.log(JSON.stringify(payload, null, 2));
121
+ return payload.status === "success" ? 0 : 1;
122
+ }
@@ -0,0 +1,24 @@
1
+ import type { CAC } from "cac";
2
+ import type { AffectedResolutionDeps } from "#git/affected";
3
+ /**
4
+ * `wp rust-check` — the Rust counterpart of `wp typecheck`/`wp test`: proves
5
+ * an affected tracked Rust crate still compiles and passes its tests.
6
+ *
7
+ * Exists because `wp ci-preflight`'s other stages only resolve TS/JS
8
+ * affectedness, so a Rust-only branch previously reported every stage as
9
+ * "no affected files" and exited green without ever invoking `cargo` — a
10
+ * silent false pass (see `catalog/agent/rules/ci-cost-local-first.md`).
11
+ * This command always prints an explicit status line, whether or not it
12
+ * found Rust crates to check, so "green" is never ambiguous.
13
+ */
14
+ export declare const RUST_CHECK_COMMAND_HELP: string;
15
+ interface RustCheckCommandDeps {
16
+ readonly getGitTopLevel?: AffectedResolutionDeps["getGitTopLevel"];
17
+ readonly getStagedFiles?: AffectedResolutionDeps["getStagedFiles"];
18
+ readonly getBranchChangedFiles?: AffectedResolutionDeps["getBranchChangedFiles"];
19
+ readonly runCargo?: (args: readonly string[], cwd: string) => {
20
+ readonly exitCode: number;
21
+ };
22
+ }
23
+ export declare function registerRustCheckCommand(cli: CAC, deps?: RustCheckCommandDeps): void;
24
+ export {};
@@ -0,0 +1,110 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { addAffectedOptions, resolveAffectedTargets } from "#git/affected";
3
+ import { resolveAffectedRustManifests, trackedCrateForManifest } from "#rust/affected-manifests";
4
+ /**
5
+ * `wp rust-check` — the Rust counterpart of `wp typecheck`/`wp test`: proves
6
+ * an affected tracked Rust crate still compiles and passes its tests.
7
+ *
8
+ * Exists because `wp ci-preflight`'s other stages only resolve TS/JS
9
+ * affectedness, so a Rust-only branch previously reported every stage as
10
+ * "no affected files" and exited green without ever invoking `cargo` — a
11
+ * silent false pass (see `catalog/agent/rules/ci-cost-local-first.md`).
12
+ * This command always prints an explicit status line, whether or not it
13
+ * found Rust crates to check, so "green" is never ambiguous.
14
+ */
15
+ export const RUST_CHECK_COMMAND_HELP = [
16
+ "Prove affected tracked Rust crates compile and pass tests (cargo check + cargo test).",
17
+ "",
18
+ "Examples:",
19
+ " wp rust-check --affected # staged changed files → owning crate(s)",
20
+ " wp rust-check --affected --branch # changed vs origin/${GITHUB_BASE_REF:-main}",
21
+ "",
22
+ "Tracked crates: native/session-memory-engine, native/dash-tui,",
23
+ "apps/desktop/src-tauri. spikes/** crates are intentionally excluded — no CI",
24
+ "workflow builds or tests them today.",
25
+ ].join("\n");
26
+ export function registerRustCheckCommand(cli, deps = {}) {
27
+ addAffectedOptions(cli.command("rust-check", RUST_CHECK_COMMAND_HELP)).action(async (flags) => {
28
+ const affected = Boolean(flags.affected);
29
+ const branch = Boolean(flags.branch);
30
+ const cwd = process.cwd();
31
+ if (!affected && !branch) {
32
+ console.error("wp rust-check requires --affected (optionally with --branch).");
33
+ return 1;
34
+ }
35
+ const resolution = resolveAffectedTargets({
36
+ commandName: "rust-check",
37
+ cwd,
38
+ affected,
39
+ branch,
40
+ policy: "fail-closed",
41
+ mapChangedFiles: resolveAffectedRustManifests,
42
+ emptyMessage: rustCheckEmptyMessage,
43
+ degradedFallbackMessage: rustCheckDegradedMessage,
44
+ degradedFailClosedMessage: rustCheckDegradedMessage,
45
+ }, deps);
46
+ if (resolution.kind === "invalid") {
47
+ console.error(resolution.message);
48
+ return 1;
49
+ }
50
+ if (resolution.kind === "degraded-fail-closed" || resolution.kind === "degraded-fallback") {
51
+ console.error(resolution.message);
52
+ return 1;
53
+ }
54
+ if (resolution.kind === "empty") {
55
+ console.log(resolution.message);
56
+ return 0;
57
+ }
58
+ if (resolution.kind === "disabled") {
59
+ // Unreachable: the --affected/--branch guard above always sets
60
+ // `affected: true` before calling resolveAffectedTargets. Handled
61
+ // for exhaustiveness only.
62
+ console.error("wp rust-check requires --affected (optionally with --branch).");
63
+ return 1;
64
+ }
65
+ const manifests = resolution.targets;
66
+ console.log(`wp rust-check: ${manifests.length} tracked Rust crate(s) affected: ${manifests.join(", ")}`);
67
+ const runCargo = deps.runCargo ?? defaultRunCargo;
68
+ for (const manifest of manifests) {
69
+ const crate = trackedCrateForManifest(manifest);
70
+ if (!crate) {
71
+ console.error(`wp rust-check: no tracked crate metadata for ${manifest}.`);
72
+ return 1;
73
+ }
74
+ console.log(`wp rust-check: cargo check --manifest-path ${crate.manifest}`);
75
+ const checkResult = runCargo(["check", "--manifest-path", crate.manifest], resolution.cwd);
76
+ if (checkResult.exitCode !== 0) {
77
+ console.error(`wp rust-check: cargo check failed for ${crate.manifest}.`);
78
+ return checkResult.exitCode;
79
+ }
80
+ console.log(`wp rust-check: cargo ${crate.testArgs.join(" ")}`);
81
+ const testResult = runCargo(crate.testArgs, resolution.cwd);
82
+ if (testResult.exitCode !== 0) {
83
+ console.error(`wp rust-check: cargo test failed for ${crate.manifest}.`);
84
+ return testResult.exitCode;
85
+ }
86
+ }
87
+ console.log(`wp rust-check: ${manifests.length} crate(s) passed: ${manifests.join(", ")}`);
88
+ return 0;
89
+ });
90
+ }
91
+ function rustCheckEmptyMessage(scope) {
92
+ const suffix = scope === "branch" ? "changed vs base ref" : "staged";
93
+ return (`wp rust-check: 0 tracked Rust crates affected (no ${suffix} files under ` +
94
+ "native/session-memory-engine, native/dash-tui, or apps/desktop/src-tauri; " +
95
+ "spikes/** Rust changes, if any, are intentionally not gated) — skipping cargo.");
96
+ }
97
+ function rustCheckDegradedMessage(reason) {
98
+ return `Unable to determine affected Rust files (${reason}); refusing to skip cargo verification.`;
99
+ }
100
+ function defaultRunCargo(args, cwd) {
101
+ const result = spawnSync("cargo", args, { cwd, stdio: "inherit" });
102
+ if (result.error) {
103
+ const isMissingBinary = result.error.code === "ENOENT";
104
+ console.error(isMissingBinary
105
+ ? "wp rust-check: cargo not found on PATH; install the Rust toolchain (https://rustup.rs) to verify Rust changes."
106
+ : `wp rust-check: ${result.error.message}`);
107
+ return { exitCode: 1 };
108
+ }
109
+ return { exitCode: typeof result.status === "number" ? result.status : 1 };
110
+ }
@@ -8,7 +8,7 @@ export function getSetupHelpText(version) {
8
8
  "Options:",
9
9
  " --with <skills> Comma-separated opt-in skills and/or presets to install (non-interactive). Presets: example-skill, lore-commits, playwright-mcp, rtk, vision plus the special token `all`. Legacy compatibility presets are intentionally omitted; use wp install oh-my opencode when you want WP-owned refreshes. Opt-in skills are listed by 'wp skill list'. ",
10
10
  " --without <skills> Comma-separated opt-in skills and/or presets to opt out of for this run. ",
11
- " --host <hosts> Comma-separated host targets: codex, claude, opencode, all, none ",
11
+ " --host <hosts> Comma-separated host targets: codex, claude, opencode, grok, all, none ",
12
12
  " --dry-run Show what would change without writing anything ",
13
13
  " --cwd <dir> Working tree to scaffold into (default: process.cwd()) ",
14
14
  " -h, --help Display this message ",
@@ -5,7 +5,7 @@
5
5
  const PRIMARY_SETUP_OPTIONS = [
6
6
  " --with <skills> Comma-separated opt-in skills and/or presets to install (non-interactive). Presets: example-skill, lore-commits, playwright-mcp, rtk, vision plus the special token `all`. Legacy compatibility presets are intentionally omitted; use wp install oh-my opencode when you want WP-owned refreshes. Opt-in skills are listed by 'wp skill list'. ",
7
7
  " --without <skills> Comma-separated opt-in skills and/or presets to opt out of for this run. ",
8
- " --host <hosts> Comma-separated host targets: codex, claude, opencode, all, none ",
8
+ " --host <hosts> Comma-separated host targets: codex, claude, opencode, grok, all, none ",
9
9
  " --prune Widen stale agent-kit plugin-cache pruning from the default host set (claude, codex, opencode) to every supported host while retaining recent versions ",
10
10
  " --dry-run Show what would change without writing anything ",
11
11
  " --cwd <dir> Working tree to scaffold into (default: process.cwd()) ",
@@ -6,6 +6,7 @@ export interface TypecheckOptions {
6
6
  readonly cwd?: string;
7
7
  readonly files?: readonly string[];
8
8
  readonly packages?: readonly string[];
9
+ readonly tests?: boolean;
9
10
  }
10
11
  interface TypecheckCommandDeps {
11
12
  readonly getGitTopLevel?: AffectedResolutionDeps["getGitTopLevel"];
@@ -23,6 +23,8 @@ export const TYPECHECK_COMMAND_HELP = [
23
23
  " wp typecheck --affected # staged changed source → reverse-importer closure",
24
24
  " wp typecheck --affected --branch # changed vs origin/${GITHUB_BASE_REF:-main}",
25
25
  " wp typecheck --pretty",
26
+ " wp typecheck --tests # test-surface lane (tsconfig.test.json); required in",
27
+ " # not part of the default gate or ci-preflight yet",
26
28
  ].join("\n");
27
29
  export function registerTypecheckCommand(cli, deps = {}) {
28
30
  addAffectedOptions(cli
@@ -31,6 +33,7 @@ export function registerTypecheckCommand(cli, deps = {}) {
31
33
  .option("--package <name>", "Run the owning-scope typecheck for an exact package.json name (repeatable)"))
32
34
  .option("--pretty", "Keep TypeScript pretty output enabled")
33
35
  .option("--full", "Print the full raw output instead of the default summary-first view")
36
+ .option("--tests", "Run the test-surface lane (tsconfig.test.json); required by ci-preflight and CI")
34
37
  .action(async (targetsOrFlags, maybeFlags) => {
35
38
  const positionalTargets = Array.isArray(targetsOrFlags)
36
39
  ? targetsOrFlags.filter((target) => typeof target === "string")
@@ -45,9 +48,29 @@ export function registerTypecheckCommand(cli, deps = {}) {
45
48
  const packages = toArray(flags.package);
46
49
  const affected = Boolean(flags.affected);
47
50
  const branch = Boolean(flags.branch);
51
+ const tests = Boolean(flags.tests);
48
52
  const cwd = process.cwd();
49
53
  let files = explicitFiles;
50
54
  let executionCwd = cwd;
55
+ if (tests && (affected || branch || explicitFiles.length > 0 || packages.length > 0)) {
56
+ console.error("--tests cannot be combined with --affected, --branch, --file, or --package.");
57
+ return 1;
58
+ }
59
+ if (tests) {
60
+ const result = await runTypecheckCommand({
61
+ pretty: Boolean(flags.pretty),
62
+ tests: true,
63
+ cwd,
64
+ });
65
+ emitCliCommandOutput({
66
+ entry: result.entry,
67
+ summary: result.entry.summary ?? "",
68
+ passed: result.exitCode === 0,
69
+ full: Boolean(flags.full),
70
+ toolName: "wp_typecheck",
71
+ });
72
+ return result.exitCode;
73
+ }
51
74
  if (affected || branch) {
52
75
  const resolution = resolveAffectedTargets({
53
76
  commandName: "typecheck",
@@ -131,9 +154,14 @@ export async function runTypecheckCommand(options = {}) {
131
154
  options.packages.length > 0) {
132
155
  throw new Error("Cannot use both --file and --package for typecheck targeting.");
133
156
  }
157
+ if (options.tests && ((options.files?.length ?? 0) > 0 || (options.packages?.length ?? 0) > 0)) {
158
+ throw new Error("Cannot use --tests together with --file or --package.");
159
+ }
134
160
  const cwd = options.cwd ?? process.cwd();
135
161
  repairCurrentManagedWorktreeDependencies(cwd);
136
- const targeted = (options.files?.length ?? 0) > 0 || (options.packages?.length ?? 0) > 0;
162
+ const targeted = (options.files?.length ?? 0) > 0 ||
163
+ (options.packages?.length ?? 0) > 0 ||
164
+ Boolean(options.tests);
137
165
  const repoRoot = resolveProjectRoot({ cwd });
138
166
  const plan = targeted
139
167
  ? planTypecheckExecution({
@@ -141,6 +169,7 @@ export async function runTypecheckCommand(options = {}) {
141
169
  defaultScopeRoot: cwd,
142
170
  files: options.files,
143
171
  packages: options.packages,
172
+ tests: options.tests,
144
173
  pretty: options.pretty,
145
174
  })
146
175
  : {
@@ -14,6 +14,12 @@ export interface WorktreeCommandOptions {
14
14
  deps?: WorktreeDependencyHydration;
15
15
  cwd?: string;
16
16
  repo?: string;
17
+ /** `prune` only: filesystem-level orphan sweep instead of registry-only prune. */
18
+ orphans?: boolean;
19
+ /** `prune --orphans` only: scope the sweep to one repo namespace. */
20
+ repoId?: string;
21
+ /** `prune --orphans` only: alias for `--force` (execute the removal plan). */
22
+ yes?: boolean;
17
23
  }
18
24
  export interface WorktreeEntry {
19
25
  path: string;
@@ -2,9 +2,10 @@
2
2
  * `wp worktree` subcommand dispatch.
3
3
  */
4
4
  import { execFileSync, spawnSync } from "node:child_process";
5
- import { existsSync, mkdirSync } from "node:fs";
5
+ import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
6
6
  import { basename, dirname, join, relative, resolve } from "node:path";
7
7
  import { findGitRoot } from "#cli/commands/init/detect-consumer.js";
8
+ import { removeCodexProjectTrust, removeCodexProjectTrusts, } from "#worktrees/codex-project-trust.js";
8
9
  import { shortId } from "#shared-utils/short-id.js";
9
10
  import { readConfig } from "#cli/commands/init/config";
10
11
  import { getProjectRoot } from "#cli/utils";
@@ -15,7 +16,8 @@ import { hydrateWorktreeDependencies, reconcileWorktreeDependencies, } from "#wo
15
16
  import { projectWorktreeAgentSurfaces } from "#worktrees/project-agent-surfaces.js";
16
17
  import { assertNotLocalMainBranch } from "#worktrees/main-ownership.js";
17
18
  import { buildWorktreeInventory, canonicalizeWorktreePath, classifyMembership, isManagedWorktreePath, resolveRepoIdentity, } from "#worktrees/identity.js";
18
- import { findRegistryCandidatesByPath, pruneStaleWorktreeRegistryEntries, readWorktreeRegistry, removeWorktreeRegistryEntries, repoScopedPathPredicate, upsertWorktreeRegistryEntry, } from "#worktrees/registry.js";
19
+ import { findRegistryCandidatesByPath, planStaleWorktreeRegistryPrune, readWorktreeRegistry, removeWorktreeRegistryEntries, repoScopedPathPredicate, upsertWorktreeRegistryEntry, } from "#worktrees/registry.js";
20
+ import { buildRegistryRepoRootMap, classifyOrphanCandidate, discoverOrphanScanCandidates, resolveRepoRootFromGitPointer, } from "#worktrees/orphan-scan.js";
19
21
  function parseDepsOption(value) {
20
22
  if (value === undefined || value === null || value === "")
21
23
  return undefined;
@@ -443,7 +445,7 @@ function handleRefresh(opts) {
443
445
  }
444
446
  console.log(`Refreshed ${updated} managed worktree entr${updated === 1 ? "y" : "ies"} for ${repoRoot}.`);
445
447
  }
446
- function handlePrune(opts) {
448
+ async function handlePrune(opts) {
447
449
  if (!opts.all)
448
450
  throw new Error("Usage: wp worktree prune --all");
449
451
  // Repository-scoped, three-state prune: only THIS repo's entries that are
@@ -451,10 +453,12 @@ function handlePrune(opts) {
451
453
  // other-repo, and live-legacy entries are retained.
452
454
  const repoRoot = resolveWorktreeRepoRoot(opts.repo ?? opts.cwd ?? process.cwd());
453
455
  const inventory = buildWorktreeInventory(repoRoot);
454
- const result = pruneStaleWorktreeRegistryEntries({
456
+ const result = planStaleWorktreeRegistryPrune({
455
457
  inventory,
456
458
  scope: { authoritativeRepoKey: resolveRepoIdentity(repoRoot) },
457
459
  });
460
+ await removeCodexProjectTrusts(result.removed.map((entry) => entry.path));
461
+ result.commit();
458
462
  console.log(`Pruned ${result.removed.length} stale managed registry entr${result.removed.length === 1 ? "y" : "ies"}.`);
459
463
  for (const warning of result.warnings)
460
464
  console.log(`warning: ${warning}`);
@@ -527,7 +531,7 @@ function handleRebind(args, opts) {
527
531
  console.log(` worktree_owner_branch: ${binding.branch}`);
528
532
  console.log(` path: ${binding.path}`);
529
533
  }
530
- function handleRemove(nameOrPath, opts) {
534
+ async function handleRemove(nameOrPath, opts) {
531
535
  const cwd = opts.cwd ?? process.cwd();
532
536
  const callerRoot = resolveWorktreeRepoRoot(cwd);
533
537
  const entries = listEntries(callerRoot);
@@ -548,6 +552,7 @@ function handleRemove(nameOrPath, opts) {
548
552
  if (result.status !== 0) {
549
553
  throw new Error("git worktree remove failed");
550
554
  }
555
+ await removeCodexProjectTrust(resolved);
551
556
  removeWorktreeRegistryEntries(repoScopedPathPredicate(resolved, { authoritativeRepoKey: resolveRepoIdentity(repoRoot) }));
552
557
  }
553
558
  function isDirty(path) {
@@ -563,7 +568,114 @@ function currentBranch(repoRoot) {
563
568
  });
564
569
  return result.status === 0 ? String(result.stdout ?? "").trim() : "";
565
570
  }
566
- function handleMergeCleanup(nameOrPath, opts) {
571
+ function formatOrphanScanBytes(bytes) {
572
+ if (bytes === "unknown")
573
+ return "-";
574
+ if (bytes < 1024)
575
+ return `${bytes}B`;
576
+ const units = ["K", "M", "G", "T"];
577
+ let value = bytes / 1024;
578
+ let unitIndex = 0;
579
+ while (value >= 1024 && unitIndex < units.length - 1) {
580
+ value /= 1024;
581
+ unitIndex += 1;
582
+ }
583
+ return `${value.toFixed(1)}${units[unitIndex]}`;
584
+ }
585
+ function summarizeOrphanScanRows(rows) {
586
+ const counts = new Map();
587
+ for (const row of rows)
588
+ counts.set(row.classification, (counts.get(row.classification) ?? 0) + 1);
589
+ const removable = rows.filter((row) => row.removable).length;
590
+ const parts = [...counts.entries()].map(([classification, count]) => `${classification}=${count}`);
591
+ return `${parts.join(", ")}, removable=${removable}`;
592
+ }
593
+ function printOrphanScanTable(rows) {
594
+ if (rows.length === 0) {
595
+ console.log("No candidate directories found under managed worktree roots.");
596
+ return;
597
+ }
598
+ console.log(`${"PATH".padEnd(72)} ${"CLASS".padEnd(13)} ${"DIRTY".padEnd(8)} ${"SIZE".padEnd(7)} ${"RM".padEnd(4)} REASON`);
599
+ for (const row of rows) {
600
+ const dirty = row.dirty === "unknown" ? "unknown" : row.dirty ? "yes" : "no";
601
+ console.log(`${row.path.padEnd(72)} ${row.classification.padEnd(13)} ${dirty.padEnd(8)} ${formatOrphanScanBytes(row.sizeBytes).padEnd(7)} ${(row.removable ? "yes" : "no").padEnd(4)} ${row.reason}`);
602
+ }
603
+ console.log("");
604
+ console.log(`Summary: ${summarizeOrphanScanRows(rows)}`);
605
+ }
606
+ function resolveOrphanScanDeps() {
607
+ const registryMap = buildRegistryRepoRootMap(readWorktreeRegistry().entries);
608
+ const readGitPointer = (path) => {
609
+ try {
610
+ return readFileSync(path, "utf8");
611
+ }
612
+ catch {
613
+ return undefined;
614
+ }
615
+ };
616
+ return {
617
+ resolveRepoRoot: (candidate) => registryMap.get(candidate.repoNamespace) ??
618
+ resolveRepoRootFromGitPointer(candidate.path, readGitPointer),
619
+ repoRootExists: existsSync,
620
+ buildInventory: buildWorktreeInventory,
621
+ probeDirty: (path) => {
622
+ const result = spawnSync("git", ["status", "--porcelain"], {
623
+ cwd: path,
624
+ encoding: "utf8",
625
+ timeout: 5000,
626
+ });
627
+ if (result.status !== 0)
628
+ return "unknown";
629
+ return String(result.stdout ?? "").trim().length > 0;
630
+ },
631
+ // Native `du` traverses in kernel/C code (no per-file V8 stat overhead),
632
+ // which is materially faster than the pure-JS walker at the scale a real
633
+ // managed-worktree tree reaches (hundreds of `node_modules` trees). This
634
+ // is only invoked for already-confirmed `orphan` candidates (see
635
+ // `finalizeRow` in orphan-scan.ts), so it never runs against `alive`
636
+ // worktrees. Bounded, non-retrying: a slow/failed probe throws and
637
+ // reports `sizeBytes: "unknown"` rather than blocking the scan.
638
+ measureSize: (path) => {
639
+ const result = spawnSync("du", ["-sk", path], { encoding: "utf8", timeout: 10000 });
640
+ if (result.status !== 0) {
641
+ throw new Error(`du -sk failed or timed out for ${path}`);
642
+ }
643
+ const kilobytes = Number.parseInt(String(result.stdout ?? "")
644
+ .trim()
645
+ .split(/\s+/u)[0] ?? "", 10);
646
+ if (Number.isNaN(kilobytes))
647
+ throw new Error(`du -sk produced unparseable output for ${path}`);
648
+ return kilobytes * 1024;
649
+ },
650
+ };
651
+ }
652
+ /**
653
+ * Filesystem-level orphan sweep: dry-run by DEFAULT, prints a classification
654
+ * table, and only removes directories (via `--force`/`--yes`) that are proven
655
+ * `orphan` (absent from their resolved primary repo's `git worktree list`)
656
+ * AND proven clean (`git status --porcelain` ran successfully with no
657
+ * output). `broken` and `unrecognized` rows are always report-only. See
658
+ * `#worktrees/orphan-scan.js` for the full classification contract.
659
+ */
660
+ async function handlePruneOrphans(opts) {
661
+ const roots = resolveKnownManagedWorktreeRoots();
662
+ const candidates = discoverOrphanScanCandidates(roots, { repoNamespaceFilter: opts.repoId });
663
+ const deps = resolveOrphanScanDeps();
664
+ const rows = candidates.map((candidate) => classifyOrphanCandidate(candidate, deps));
665
+ printOrphanScanTable(rows);
666
+ if (!(opts.force === true || opts.yes === true)) {
667
+ console.log("");
668
+ console.log("Dry run only. Re-run with --force (or --yes) to remove the directories marked removable=yes.");
669
+ return;
670
+ }
671
+ const removable = rows.filter((row) => row.removable);
672
+ for (const row of removable) {
673
+ rmSync(row.path, { recursive: true, force: true });
674
+ console.log(`Removed ${row.path}`);
675
+ }
676
+ console.log(`Removed ${removable.length} orphan director${removable.length === 1 ? "y" : "ies"}.`);
677
+ }
678
+ async function handleMergeCleanup(nameOrPath, opts) {
567
679
  const cwd = opts.cwd ?? process.cwd();
568
680
  const callerRoot = resolveWorktreeRepoRoot(cwd);
569
681
  const entries = listEntries(callerRoot);
@@ -628,6 +740,7 @@ function handleMergeCleanup(nameOrPath, opts) {
628
740
  if (removeResult.status !== 0) {
629
741
  throw new Error("git worktree remove failed");
630
742
  }
743
+ await removeCodexProjectTrust(resolved);
631
744
  removeWorktreeRegistryEntries(repoScopedPathPredicate(resolved, { authoritativeRepoKey: resolveRepoIdentity(repoRoot) }));
632
745
  const fetchResult = spawnSync("git", [...plan.fetchArgs], { cwd: repoRoot, stdio: "inherit" });
633
746
  if (fetchResult.status !== 0) {
@@ -665,7 +778,11 @@ export async function executeWorktreeSubcommand(subcommand, args, opts) {
665
778
  handleRefresh(opts);
666
779
  return;
667
780
  case "prune":
668
- handlePrune(opts);
781
+ if (opts.orphans) {
782
+ await handlePruneOrphans(opts);
783
+ return;
784
+ }
785
+ await handlePrune(opts);
669
786
  return;
670
787
  case "migrate":
671
788
  handleMigrate(opts);
@@ -682,7 +799,7 @@ export async function executeWorktreeSubcommand(subcommand, args, opts) {
682
799
  if (!nameOrPath) {
683
800
  throw new Error("Usage: wp worktree remove <branch-or-path> [--force]");
684
801
  }
685
- handleRemove(nameOrPath, opts);
802
+ await handleRemove(nameOrPath, opts);
686
803
  return;
687
804
  }
688
805
  case "merge-cleanup": {
@@ -690,7 +807,7 @@ export async function executeWorktreeSubcommand(subcommand, args, opts) {
690
807
  if (!nameOrPath) {
691
808
  throw new Error("Usage: wp worktree merge-cleanup <branch-or-path> [--base <ref>] [--dry-run]");
692
809
  }
693
- handleMergeCleanup(nameOrPath, opts);
810
+ await handleMergeCleanup(nameOrPath, opts);
694
811
  return;
695
812
  }
696
813
  default: {
@@ -8,6 +8,10 @@ const HELP_TEXT = [
8
8
  " list [--all] List repo or global managed worktrees",
9
9
  " refresh [--all|--repo <dir>] Refresh managed registry from git",
10
10
  " prune --all Prune stale managed registry entries",
11
+ " prune --orphans [--repo-id <id>] [--force|--yes]",
12
+ " Scan managed roots for on-disk dirs absent",
13
+ " from git worktree list; dry-run by default,",
14
+ " removes only proven-clean orphans with --force",
11
15
  " migrate Move legacy sibling _worktrees",
12
16
  " adopt <blueprint-slug> <path> Claim existing checkout as owner",
13
17
  " rebind <blueprint-slug> [--path <path>] Repair owner metadata",
@@ -24,6 +28,9 @@ const HELP_TEXT = [
24
28
  " --no-setup Skip configured worktree.setup scripts (new only)",
25
29
  " --force Skip configured teardown scripts and force removal",
26
30
  " --stash-primary Stash dirty primary state and switch to the expected primary branch before merge-cleanup",
31
+ " --orphans Filesystem-level orphan sweep for prune (see prune --orphans above)",
32
+ " --repo-id <id> Scope prune --orphans to one repo namespace under repos/<id>",
33
+ " --yes Alias for --force (prune --orphans only)",
27
34
  " --cwd <dir> Repo root (default: process.cwd())",
28
35
  ].join("\n");
29
36
  export function registerWorktreeRouter(cli) {
@@ -40,6 +47,9 @@ export function registerWorktreeRouter(cli) {
40
47
  .option("--stash-primary", "Stash dirty primary state and switch to the expected primary branch before merge-cleanup")
41
48
  .option("--all", "Use global managed inventory where supported")
42
49
  .option("--repo <dir>", "Repo root for refresh/rebind (default: cwd)")
50
+ .option("--orphans", "Filesystem-level orphan sweep for prune (dry-run unless --force/--yes)")
51
+ .option("--repo-id <id>", "Scope prune --orphans to one repo namespace under repos/<id>")
52
+ .option("--yes", "Alias for --force (prune --orphans only)")
43
53
  .option("--cwd <dir>", "Repo root to operate from (default: process.cwd())")
44
54
  .action(async (subcommand, args, options) => {
45
55
  if (!subcommand) {
@@ -79,8 +79,8 @@ function validateDirectProviderLaunch(provider, cwd) {
79
79
  ? `Run \`${installCommand}\` and retry.`
80
80
  : `Install ${ownershipName} and retry.`);
81
81
  }
82
- // External PATH hosts (Grok) do not run optional-tool freshness — skip vp.
83
- const vpCommand = resolveGlobalCapableVpCommand();
82
+ // External PATH hosts (Grok) do not run optional-tool freshness.
83
+ const vpCommand = adapter ? resolveGlobalCapableVpCommand() : null;
84
84
  if (adapter && !vpCommand) {
85
85
  throw new DirectProviderLaunchPreparationError("No global-capable Vite+ runner found.", "Reinstall @webpresso/agent-kit and retry.");
86
86
  }
@@ -105,10 +105,14 @@ function ensureCodexSessionTooling(repoRoot) {
105
105
  throw new DirectProviderLaunchPreparationError(`Codex project MCP server ${project.server} is unavailable: ${project.reason}`, "Run `wp setup --yes-global` from this repo to provision project MCP servers, then retry.");
106
106
  }
107
107
  }
108
+ const PROVIDER_SESSION_PREFLIGHTS = {
109
+ claude: () => undefined,
110
+ codex: ensureCodexSessionTooling,
111
+ grok: () => undefined,
112
+ opencode: () => undefined,
113
+ };
108
114
  function ensureProviderSessionTooling(provider, cwd) {
109
- if (provider !== "codex")
110
- return;
111
- ensureCodexSessionTooling(cwd);
115
+ PROVIDER_SESSION_PREFLIGHTS[provider](cwd);
112
116
  }
113
117
  export function isBareInteractiveProviderLaunch(args) {
114
118
  return parseDashPermissionMode(args).args.length === 0;