@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
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Guardrail: keep tests from silently growing multi-second / multi-ten-second
3
+ * full-repo filesystem scans, and from "fixing" that cost with elevated
4
+ * per-test timeouts.
5
+ *
6
+ * The private-UI cutover anti-regression suite originally walked the monorepo
7
+ * with readdirSync/statSync/readFileSync and needed a 30s timeout. Prefer
8
+ * `git grep` (tracked files) or a bounded fixture tree instead.
9
+ *
10
+ * Opt-out: `// test-scan-perf-allow` on the same line or previous line as the
11
+ * walk/timeout site, or `// test-scan-perf-allow-file` anywhere in the file.
12
+ */
13
+ import { execFileSync } from "node:child_process";
14
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
15
+ import { join, relative } from "node:path";
16
+ const TITLE = "test-scan-perf";
17
+ const TEST_FILE_PATTERN = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
18
+ const E2E_FILE_PATTERN = /\.e2e\./u;
19
+ const SKIP_DIRS = new Set([
20
+ ".git",
21
+ "node_modules",
22
+ "dist",
23
+ "coverage",
24
+ ".turbo",
25
+ ".webpresso",
26
+ ".agent",
27
+ ".agents",
28
+ ".claude",
29
+ ".codex",
30
+ ".cursor",
31
+ ".opencode",
32
+ ".omx",
33
+ "playwright-report",
34
+ "test-results",
35
+ "logs",
36
+ "_worktrees",
37
+ ]);
38
+ const ALLOW_LINE_PATTERN = /test-scan-perf-allow(?:-timeout|-walk)?\b/iu;
39
+ const ALLOW_FILE_PATTERN = /test-scan-perf-allow-file\b/iu;
40
+ /** Recursive walk helper that also reads file contents (content scan). */
41
+ const RECURSIVE_WALK_HINT = /(?:function\s+walk\b|const\s+walk\s*=|let\s+walk\s*=|var\s+walk\s*=)/u;
42
+ const RECURSIVE_CALL_HINT = /\bwalk\s*\(\s*(?:full|dir|current|directory|entryPath|sub|next)\b/u;
43
+ const READDIR_HINT = /\breaddirSync\b/u;
44
+ const READ_FILE_HINT = /\breadFileSync\b/u;
45
+ /**
46
+ * True when the file acquires the *workspace* root (not a mkdtemp fixture)
47
+ * and feeds that root into a recursive walk / file-finder helper.
48
+ */
49
+ function hasWorkspaceRootScanStart(code) {
50
+ const assignsWorkspaceRoot = /\bfindRepoRoot\s*\(/u.test(code) ||
51
+ /(?:const|let|var)\s+(?:repoRoot|REPO_ROOT)\s*=\s*process\.cwd\s*\(/u.test(code) ||
52
+ /(?:const|let|var)\s+root\s*=\s*(?:findRepoRoot\s*\(|process\.cwd\s*\()/u.test(code);
53
+ if (!assignsWorkspaceRoot)
54
+ return false;
55
+ // Direct walk(repoRoot|REPO_ROOT) or helper(repoRoot|REPO_ROOT) that owns the walk.
56
+ if (/\b(?:walk|findFiles|list\w*|scan\w*|collect\w*)\s*\(\s*(?:repoRoot|REPO_ROOT)\b/u.test(code)) {
57
+ return true;
58
+ }
59
+ // walk(root) only when `root` itself is the workspace root assignment.
60
+ if (/(?:const|let|var)\s+root\s*=\s*(?:findRepoRoot\s*\(|process\.cwd\s*\()/u.test(code) &&
61
+ /\b(?:walk|findFiles|list\w*|scan\w*|collect\w*)\s*\(\s*root\b/u.test(code)) {
62
+ return true;
63
+ }
64
+ return false;
65
+ }
66
+ const MIN_ELEVATED_TIMEOUT_MS = 30_000;
67
+ export function isTestScanPerfTargetPath(relativePath) {
68
+ const normalized = relativePath.replaceAll("\\", "/");
69
+ if (!TEST_FILE_PATTERN.test(normalized))
70
+ return false;
71
+ if (E2E_FILE_PATTERN.test(normalized))
72
+ return false;
73
+ // Audit self-tests and fixtures intentionally include positive examples.
74
+ if (normalized.includes("/__fixtures__/"))
75
+ return false;
76
+ if (normalized.endsWith("/test-scan-perf.test.ts"))
77
+ return false;
78
+ return true;
79
+ }
80
+ function stripComments(source) {
81
+ return source.replace(/\/\*[\s\S]*?\*\//gu, "").replace(/(^|[^:])\/\/[^\n]*/gu, "$1");
82
+ }
83
+ function stripAllowedLines(source) {
84
+ const lines = source.split(/\r?\n/u);
85
+ return lines.map((line, index) => (lineAllowed(lines, index) ? "" : line)).join("\n");
86
+ }
87
+ function lineAllowed(lines, index) {
88
+ const current = lines[index] ?? "";
89
+ if (ALLOW_LINE_PATTERN.test(current))
90
+ return true;
91
+ if (index > 0 && ALLOW_LINE_PATTERN.test(lines[index - 1] ?? ""))
92
+ return true;
93
+ return false;
94
+ }
95
+ function hasRepoRootContentWalk(code) {
96
+ if (!READDIR_HINT.test(code) || !READ_FILE_HINT.test(code))
97
+ return false;
98
+ if (!RECURSIVE_WALK_HINT.test(code))
99
+ return false;
100
+ const recursiveCall = RECURSIVE_CALL_HINT.test(code) || /\bwalk\s*\(\s*join\s*\(/u.test(code);
101
+ if (!recursiveCall)
102
+ return false;
103
+ return hasWorkspaceRootScanStart(code);
104
+ }
105
+ function parseTimeoutMs(raw) {
106
+ const digits = raw.replaceAll("_", "");
107
+ if (!/^\d+$/u.test(digits))
108
+ return null;
109
+ const value = Number(digits);
110
+ return Number.isFinite(value) ? value : null;
111
+ }
112
+ function findElevatedTimeoutLines(text) {
113
+ const hits = [];
114
+ const lines = text.split(/\r?\n/u);
115
+ // Line-oriented: `{ timeout: 30000 }`, trailing `, 30000)`, or a multi-line
116
+ // `it(\n "…",\n () => {…},\n 30_000,\n)` form.
117
+ for (let index = 0; index < lines.length; index += 1) {
118
+ const line = lines[index] ?? "";
119
+ if (lineAllowed(lines, index))
120
+ continue;
121
+ const optionMatch = line.match(/timeout\s*:\s*([0-9_]+)/u);
122
+ if (optionMatch?.[1]) {
123
+ const ms = parseTimeoutMs(optionMatch[1]);
124
+ if (ms !== null && ms >= MIN_ELEVATED_TIMEOUT_MS) {
125
+ hits.push({ line: index + 1, ms });
126
+ continue;
127
+ }
128
+ }
129
+ // Same-line trailing timeout: `}, 30_000)` / `, 30000)`
130
+ const trailingMatch = line.match(/,\s*([0-9_]+)\s*\)\s*;?\s*$/u);
131
+ // Own-line timeout before closing paren: ` 30_000,` / ` 30000`
132
+ const ownLineMatch = line.match(/^\s*([0-9_]+)\s*,?\s*$/u);
133
+ const raw = trailingMatch?.[1] ?? ownLineMatch?.[1];
134
+ if (!raw)
135
+ continue;
136
+ const ms = parseTimeoutMs(raw);
137
+ const windowStart = Math.max(0, index - 16);
138
+ const windowEnd = Math.min(lines.length, index + 3);
139
+ const window = lines.slice(windowStart, windowEnd).join("\n");
140
+ if (ms !== null &&
141
+ ms >= MIN_ELEVATED_TIMEOUT_MS &&
142
+ /\b(?:it|test)(?:\.only|\.each)?\s*\(/u.test(window)) {
143
+ hits.push({ line: index + 1, ms });
144
+ }
145
+ }
146
+ // Deduplicate by line.
147
+ const seen = new Set();
148
+ return hits.filter((hit) => {
149
+ if (seen.has(hit.line))
150
+ return false;
151
+ seen.add(hit.line);
152
+ return true;
153
+ });
154
+ }
155
+ export function findTestScanPerfViolationsInText(relativePath, text) {
156
+ if (!isTestScanPerfTargetPath(relativePath))
157
+ return [];
158
+ if (ALLOW_FILE_PATTERN.test(text))
159
+ return [];
160
+ const violations = [];
161
+ const code = stripComments(stripAllowedLines(text));
162
+ const hasSlowWalk = hasRepoRootContentWalk(code);
163
+ if (hasSlowWalk) {
164
+ violations.push({
165
+ file: relativePath,
166
+ ruleId: "repo-root-recursive-content-scan",
167
+ severity: "error",
168
+ message: `${relativePath}: recursive readdir/readFile content scan from the repo root. ` +
169
+ `Use \`git grep -l\` (tracked files) or a bounded fixture tree instead of walking ` +
170
+ `the whole workspace. Opt out with \`// test-scan-perf-allow\` on the walk site ` +
171
+ `or \`// test-scan-perf-allow-file\` once with justification.`,
172
+ });
173
+ }
174
+ // Elevated timeouts are only flagged when co-located with a repo-root FS walk —
175
+ // that is the "band-aid for a slow scan" pattern. Legitimate long e2e/subprocess
176
+ // timeouts without a root walk are out of scope.
177
+ if (hasSlowWalk) {
178
+ for (const hit of findElevatedTimeoutLines(text)) {
179
+ violations.push({
180
+ file: relativePath,
181
+ line: hit.line,
182
+ ruleId: "timeout-band-aid-for-scan",
183
+ severity: "error",
184
+ message: `${relativePath}:${hit.line}: per-test timeout ${hit.ms}ms co-located with a ` +
185
+ `repo-root recursive FS scan. Fix the scan (git-grep / bounded tree) instead of ` +
186
+ `raising the timeout. Opt out with \`// test-scan-perf-allow\` on that line.`,
187
+ });
188
+ }
189
+ }
190
+ return violations;
191
+ }
192
+ function listTestFilesViaGit(root) {
193
+ try {
194
+ const stdout = execFileSync("git", ["-C", root, "ls-files", "-z"], {
195
+ encoding: "utf8",
196
+ maxBuffer: 16 * 1024 * 1024,
197
+ });
198
+ return stdout
199
+ .split("\0")
200
+ .map((entry) => entry.trim())
201
+ .filter(Boolean)
202
+ .filter((entry) => isTestScanPerfTargetPath(entry));
203
+ }
204
+ catch {
205
+ return null;
206
+ }
207
+ }
208
+ function walkTestFiles(dir, root, out) {
209
+ let entries;
210
+ try {
211
+ entries = readdirSync(dir);
212
+ }
213
+ catch {
214
+ return;
215
+ }
216
+ for (const entry of entries) {
217
+ if (SKIP_DIRS.has(entry))
218
+ continue;
219
+ const full = join(dir, entry);
220
+ let st;
221
+ try {
222
+ st = statSync(full);
223
+ }
224
+ catch {
225
+ continue;
226
+ }
227
+ if (st.isDirectory()) {
228
+ walkTestFiles(full, root, out);
229
+ continue;
230
+ }
231
+ if (!st.isFile())
232
+ continue;
233
+ const rel = relative(root, full).replaceAll("\\", "/");
234
+ if (isTestScanPerfTargetPath(rel))
235
+ out.push(rel);
236
+ }
237
+ }
238
+ function listTestFiles(root, affectedFiles) {
239
+ if (affectedFiles !== undefined) {
240
+ return affectedFiles
241
+ .map((file) => file.replaceAll("\\", "/"))
242
+ .filter((file) => isTestScanPerfTargetPath(file));
243
+ }
244
+ const fromGit = listTestFilesViaGit(root);
245
+ if (fromGit)
246
+ return fromGit;
247
+ const out = [];
248
+ walkTestFiles(root, root, out);
249
+ return out;
250
+ }
251
+ export function auditTestScanPerf(rootDirectory = process.cwd(), options = {}) {
252
+ const root = rootDirectory;
253
+ if (!existsSync(root)) {
254
+ return { ok: true, title: TITLE, checked: 0, violations: [] };
255
+ }
256
+ const files = listTestFiles(root, options.affectedFiles);
257
+ const violations = [];
258
+ for (const rel of files) {
259
+ const full = join(root, rel);
260
+ let text;
261
+ try {
262
+ text = readFileSync(full, "utf8");
263
+ }
264
+ catch {
265
+ continue;
266
+ }
267
+ violations.push(...findTestScanPerfViolationsInText(rel, text));
268
+ }
269
+ return {
270
+ ok: violations.length === 0,
271
+ title: TITLE,
272
+ checked: files.length,
273
+ violations,
274
+ };
275
+ }
@@ -133,10 +133,9 @@ function shouldSkipDirectory(name) {
133
133
  ".stryker-tmp",
134
134
  ".agent",
135
135
  ".agents",
136
- ".gemini",
137
- ".windsurf",
138
136
  ".runtime",
139
137
  ".codex",
138
+ ".grok",
140
139
  "_worktrees",
141
140
  // Gitignored Claude Code agent surface — agent worktree scratch under
142
141
  // .claude/worktrees/* carries vendored package manifests that are not the
@@ -0,0 +1,2 @@
1
+ import type { RepoAuditResult } from "./repo-guardrails.js";
2
+ export declare function auditTypescriptVersion(rootDirectory: string): RepoAuditResult;
@@ -0,0 +1,123 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
+ /**
5
+ * `wp audit typescript-version` — enforce the native TypeScript compiler major
6
+ * at consumers.
7
+ *
8
+ * agent-kit migrated itself to TypeScript 7 (the native Go compiler) and pins
9
+ * `typescript: ^7.0.2` in every shipped template, but nothing checked that a
10
+ * downstream consumer stayed on 7 — so a consumer could silently regress to a
11
+ * legacy TypeScript major (observed: edge-matte pinned `typescript: ^6.0.3`).
12
+ * This audit ships through `wp audit guardrails`, so consumers inherit it
13
+ * automatically on their next `wp` bump with no CI edits.
14
+ *
15
+ * It gates only the `typescript` compiler dependency. `@typescript/typescript6`
16
+ * — the Microsoft-published TS6 compiler-API compatibility package that
17
+ * agent-kit itself depends on until TS 7.1 stabilizes the programmatic API — is
18
+ * a distinct dependency and is never flagged.
19
+ */
20
+ const REQUIRED_MAJOR = 7;
21
+ const COMPILER_DEP = "typescript";
22
+ // Inline escape hatch for a documented, temporary exception (e.g. a consumer
23
+ // mid-migration). Placed as a comment in pnpm-workspace.yaml or in the manifest.
24
+ const OPT_OUT_MARKER = "typescript-version-allow";
25
+ /** Lowest major a version range can resolve to (the enforceable floor). */
26
+ function majorFloor(range) {
27
+ const match = /(\d+)/u.exec(range);
28
+ return match ? Number(match[1]) : undefined;
29
+ }
30
+ /** `catalog:` / `workspace:` pins resolve elsewhere; `*`/`latest` carry no floor. */
31
+ function isDelegatedPin(value) {
32
+ return (value.startsWith("catalog:") ||
33
+ value.startsWith("workspace:") ||
34
+ value === "*" ||
35
+ value === "latest");
36
+ }
37
+ function checkTypescriptPin(source, value, violations) {
38
+ if (isDelegatedPin(value))
39
+ return;
40
+ const major = majorFloor(value);
41
+ if (major === undefined || major >= REQUIRED_MAJOR)
42
+ return;
43
+ violations.push({
44
+ file: source,
45
+ severity: "error",
46
+ ruleId: "typescript-version",
47
+ message: `typescript is pinned to "${value}" (major ${major}); the native compiler ` +
48
+ `requires TypeScript ${REQUIRED_MAJOR}. Bump to ^${REQUIRED_MAJOR} (or a catalog: ref). ` +
49
+ `The @typescript/typescript6 compiler-API bridge is separate and allowed.`,
50
+ });
51
+ }
52
+ function collectManifestPaths(rootDirectory) {
53
+ const paths = [];
54
+ if (existsSync(join(rootDirectory, "package.json")))
55
+ paths.push("package.json");
56
+ for (const group of ["packages", "apps"]) {
57
+ const dir = join(rootDirectory, group);
58
+ if (!existsSync(dir))
59
+ continue;
60
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
61
+ if (!entry.isDirectory())
62
+ continue;
63
+ const manifest = join(group, entry.name, "package.json");
64
+ if (existsSync(join(rootDirectory, manifest)))
65
+ paths.push(manifest);
66
+ }
67
+ }
68
+ return paths;
69
+ }
70
+ export function auditTypescriptVersion(rootDirectory) {
71
+ const violations = [];
72
+ const sources = [];
73
+ let checked = 0;
74
+ // 1. Workspace catalog pin — the primary contract; catches catalog-level drift.
75
+ const workspacePath = join(rootDirectory, "pnpm-workspace.yaml");
76
+ if (existsSync(workspacePath)) {
77
+ const raw = readFileSync(workspacePath, "utf8");
78
+ sources.push({ path: "pnpm-workspace.yaml", kind: "pnpm-workspace" });
79
+ if (!raw.includes(OPT_OUT_MARKER)) {
80
+ const workspace = (parseYaml(raw) ?? {});
81
+ const catalogPin = workspace.catalog?.[COMPILER_DEP];
82
+ if (typeof catalogPin === "string") {
83
+ checked += 1;
84
+ checkTypescriptPin("pnpm-workspace.yaml (catalog.typescript)", catalogPin, violations);
85
+ }
86
+ // pnpm also supports NAMED catalogs (`catalogs.<name>.typescript`), a
87
+ // separate map from the default `catalog` above. A pin placed there
88
+ // resolves the same way for consumers using `catalog:<name>` refs, so it
89
+ // must be enforced identically or a consumer can dodge the check by
90
+ // moving the pin into a named catalog.
91
+ for (const [catalogName, entries] of Object.entries(workspace.catalogs ?? {})) {
92
+ const namedPin = entries[COMPILER_DEP];
93
+ if (typeof namedPin !== "string")
94
+ continue;
95
+ checked += 1;
96
+ checkTypescriptPin(`pnpm-workspace.yaml (catalogs.${catalogName}.typescript)`, namedPin, violations);
97
+ }
98
+ }
99
+ }
100
+ // 2. Direct typescript pins in package manifests (catalog:/workspace: skipped —
101
+ // they resolve through the catalog checked above).
102
+ for (const manifest of collectManifestPaths(rootDirectory)) {
103
+ const raw = readFileSync(join(rootDirectory, manifest), "utf8");
104
+ sources.push({ path: manifest, kind: "package-manifest" });
105
+ if (raw.includes(OPT_OUT_MARKER))
106
+ continue;
107
+ const parsed = JSON.parse(raw);
108
+ for (const field of ["dependencies", "devDependencies"]) {
109
+ const pin = parsed[field]?.[COMPILER_DEP];
110
+ if (typeof pin === "string") {
111
+ checked += 1;
112
+ checkTypescriptPin(`${manifest} (${field}.typescript)`, pin, violations);
113
+ }
114
+ }
115
+ }
116
+ return {
117
+ ok: violations.length === 0,
118
+ title: `TypeScript ${REQUIRED_MAJOR} consumer pin`,
119
+ checked,
120
+ sources,
121
+ violations,
122
+ };
123
+ }
@@ -74,14 +74,56 @@ export function serializeBlueprint(blueprint) {
74
74
  delete data.completed_at;
75
75
  }
76
76
  const { tasks: _tasks, task_statuses: _task_statuses, ...cleanedData } = data;
77
- return quoteGeneratedProgressScalar(matter.stringify(content, cleanedData), blueprint.progress);
77
+ const serialized = quoteGeneratedProgressScalar(matter.stringify(content, cleanedData), blueprint.progress);
78
+ return preserveOriginalFrontmatterScalars(serialized, blueprint.raw);
78
79
  }
80
+ // Frontmatter keys serializeBlueprint intentionally rewrites; every other
81
+ // scalar must survive a write byte-for-byte.
82
+ const SERIALIZE_OWNED_FRONTMATTER_KEYS = new Set([
83
+ "status",
84
+ "complexity",
85
+ "last_updated",
86
+ "progress",
87
+ "completed_at",
88
+ ]);
89
+ const FRONTMATTER_SCALAR_LINE = /^([A-Za-z0-9_]+):[ \t]+\S.*$/;
79
90
  function quoteGeneratedProgressScalar(serialized, progress) {
80
91
  if (!progress)
81
92
  return serialized;
82
93
  const escaped = progress.replace(/'/g, "''");
83
94
  return serialized.replace(/^progress: .+$/m, `progress: '${escaped}'`);
84
95
  }
96
+ // `matter.stringify` re-dumps the whole frontmatter YAML, which canonicalizes
97
+ // scalar quoting (e.g. `title: "X"` -> `title: X`, `owner: "claude"` ->
98
+ // `owner: claude`). Those bytes feed the plan-subject digest, so a cosmetic
99
+ // re-quote on a write silently invalidates a recorded review approval. Restore
100
+ // the ORIGINAL single-line scalar verbatim for every field this serializer does
101
+ // not own, so a status/progress write never disturbs `title`/`owner`/`created`
102
+ // etc. Multi-line and array fields (no inline value) are left to js-yaml, which
103
+ // dumps them deterministically. Generalizes the earlier progress-only patch.
104
+ function preserveOriginalFrontmatterScalars(serialized, originalRaw) {
105
+ const originalFrontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(originalRaw)?.[1] ?? "";
106
+ const originalScalars = new Map();
107
+ for (const line of originalFrontmatter.split("\n")) {
108
+ const match = FRONTMATTER_SCALAR_LINE.exec(line);
109
+ if (match && !SERIALIZE_OWNED_FRONTMATTER_KEYS.has(match[1])) {
110
+ originalScalars.set(match[1], line);
111
+ }
112
+ }
113
+ if (originalScalars.size === 0)
114
+ return serialized;
115
+ const block = /^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(serialized);
116
+ if (!block)
117
+ return serialized;
118
+ const rebuilt = block[2]
119
+ .split("\n")
120
+ .map((line) => {
121
+ const match = FRONTMATTER_SCALAR_LINE.exec(line);
122
+ return (match && originalScalars.get(match[1])) || line;
123
+ })
124
+ .join("\n");
125
+ return `${serialized.slice(0, block.index)}${block[1]}${rebuilt}${block[3]}${serialized.slice(block.index + block[0].length)}`;
126
+ }
85
127
  function extractCheckboxStatus(section) {
86
128
  const checkboxRegex = /^- \[([ x])\]/gm;
87
129
  const matches = Array.from(section.matchAll(checkboxRegex));
@@ -3,3 +3,17 @@
3
3
  */
4
4
  import type { CriteriaResult } from "#core/types";
5
5
  export declare function checkAcceptanceCriteria(markdown: string): CriteriaResult;
6
+ /**
7
+ * Count checked acceptance-criteria checkboxes that are NOT explained by an
8
+ * explicit `**Status:** done`/`dropped` line on their containing task.
9
+ *
10
+ * `completeTask()` (see `markdown/helpers.ts`) checks every checkbox inside
11
+ * a task's own section when that task is marked done — which is expected,
12
+ * honest progress tracking during `draft` (pretool-guard's `blueprint-first`
13
+ * validator explicitly allows implementing source while a blueprint is
14
+ * still draft). A checked box that is NOT inside a done/dropped task section
15
+ * — e.g. the top-level `## Approvals` checklist, or a task still `todo` with
16
+ * pre-checked boxes — still signals an unreviewed completion claim and must
17
+ * keep failing the draft anti-masquerade guard.
18
+ */
19
+ export declare function countUnexplainedCheckedCriteria(markdown: string): number;
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Check acceptance criteria completion in plan markdown.
3
3
  */
4
+ const CHECKBOX_REGEX = /^- \[([ x])\]/gm;
5
+ const TASK_HEADER_REGEX = /^####\s+(?:\[[^\]]+\]\s+)?Task\s+\d+(?:\.\d+)+:.*$/gm;
6
+ const TASK_DONE_OR_DROPPED_REGEX = /^\*\*Status:\*\*\s*(done|dropped)\s*$/im;
7
+ const SECTION_BOUNDARY_REGEX = /\n##\s|\n---\n/;
4
8
  export function checkAcceptanceCriteria(markdown) {
5
- const criteriaRegex = /^- \[([ x])\]/gm;
6
- const matches = Array.from(markdown.matchAll(criteriaRegex));
9
+ const matches = Array.from(markdown.matchAll(CHECKBOX_REGEX));
7
10
  const total = matches.length;
8
11
  const checked = matches.filter((m) => m[1] === "x").length;
9
12
  return {
@@ -12,3 +15,39 @@ export function checkAcceptanceCriteria(markdown) {
12
15
  allChecked: total === 0 || checked === total,
13
16
  };
14
17
  }
18
+ function countChecked(text) {
19
+ return Array.from(text.matchAll(CHECKBOX_REGEX)).filter((m) => m[1] === "x").length;
20
+ }
21
+ function taskSectionEnd(markdown, start, nextTaskStart) {
22
+ const rest = markdown.slice(start);
23
+ const boundaryMatch = rest.match(SECTION_BOUNDARY_REGEX);
24
+ const boundaryIndex = boundaryMatch?.index === undefined ? markdown.length : start + boundaryMatch.index + 1;
25
+ return Math.min(nextTaskStart, boundaryIndex);
26
+ }
27
+ function sumCheckedInDoneOrDroppedTasks(markdown) {
28
+ const headers = Array.from(markdown.matchAll(TASK_HEADER_REGEX));
29
+ return headers.reduce((sum, header, index) => {
30
+ const start = header.index ?? 0;
31
+ const nextStart = headers[index + 1]?.index ?? markdown.length;
32
+ const section = markdown.slice(start, taskSectionEnd(markdown, start, nextStart));
33
+ return TASK_DONE_OR_DROPPED_REGEX.test(section) ? sum + countChecked(section) : sum;
34
+ }, 0);
35
+ }
36
+ /**
37
+ * Count checked acceptance-criteria checkboxes that are NOT explained by an
38
+ * explicit `**Status:** done`/`dropped` line on their containing task.
39
+ *
40
+ * `completeTask()` (see `markdown/helpers.ts`) checks every checkbox inside
41
+ * a task's own section when that task is marked done — which is expected,
42
+ * honest progress tracking during `draft` (pretool-guard's `blueprint-first`
43
+ * validator explicitly allows implementing source while a blueprint is
44
+ * still draft). A checked box that is NOT inside a done/dropped task section
45
+ * — e.g. the top-level `## Approvals` checklist, or a task still `todo` with
46
+ * pre-checked boxes — still signals an unreviewed completion claim and must
47
+ * keep failing the draft anti-masquerade guard.
48
+ */
49
+ export function countUnexplainedCheckedCriteria(markdown) {
50
+ const { checked } = checkAcceptanceCriteria(markdown);
51
+ const explained = sumCheckedInDoneOrDroppedTasks(markdown);
52
+ return Math.max(0, checked - explained);
53
+ }
@@ -2,7 +2,7 @@
2
2
  * Validate plan state consistency with folder structure and checkbox status.
3
3
  */
4
4
  import { planStatusSchema } from "#core/schema";
5
- import { checkAcceptanceCriteria } from "./criteria.js";
5
+ import { checkAcceptanceCriteria, countUnexplainedCheckedCriteria } from "./criteria.js";
6
6
  /**
7
7
  * Validate plan is in completed/ folder if status is completed.
8
8
  */
@@ -22,7 +22,9 @@ function validateCompletedFolder(status, isInCompleted, criteria) {
22
22
  if (status === "completed" && !criteria.allChecked) {
23
23
  return {
24
24
  valid: false,
25
- error: `Plan has status: completed but only ${criteria.checked}/${criteria.total} acceptance criteria are checked`,
25
+ error: `Plan has status: completed but only ${criteria.checked}/${criteria.total} acceptance ` +
26
+ `criteria are checked. Check the remaining boxes once that work is genuinely done, or ` +
27
+ `set status back to in-progress until it is.`,
26
28
  };
27
29
  }
28
30
  return { valid: true };
@@ -30,7 +32,7 @@ function validateCompletedFolder(status, isInCompleted, criteria) {
30
32
  /**
31
33
  * Validate plan is in draft/ folder if status is draft.
32
34
  */
33
- function validateDraftFolder(status, isInDraft, criteria) {
35
+ function validateDraftFolder(status, isInDraft, markdown) {
34
36
  if (status === "draft" && !isInDraft) {
35
37
  return {
36
38
  valid: false,
@@ -43,11 +45,20 @@ function validateDraftFolder(status, isInDraft, criteria) {
43
45
  error: `Plan is in draft/ folder but has status: ${status} (expected status: draft)`,
44
46
  };
45
47
  }
46
- if (status === "draft" && criteria.checked > 0) {
47
- return {
48
- valid: false,
49
- error: `Plan has status: draft but ${criteria.checked} acceptance criteria are checked (expected 0)`,
50
- };
48
+ if (status === "draft") {
49
+ const unexplained = countUnexplainedCheckedCriteria(markdown);
50
+ if (unexplained > 0) {
51
+ return {
52
+ valid: false,
53
+ error: `Plan has status: draft but ${unexplained} acceptance criteria are checked outside ` +
54
+ `a task marked **Status:** done/dropped (expected 0). Marking a task's own status ` +
55
+ `done/dropped legitimately checks its boxes during draft implementation; a checked ` +
56
+ `box elsewhere (e.g. the Approvals checklist, or a still-todo task) claims unreviewed ` +
57
+ `completion. Fix by either setting the owning task's **Status:** to done/dropped if ` +
58
+ `the work is genuinely finished, unchecking the box if it isn't, or promoting the ` +
59
+ `blueprint with \`wp blueprint promote <slug>\` once it has been reviewed.`,
60
+ };
61
+ }
51
62
  }
52
63
  return { valid: true };
53
64
  }
@@ -72,11 +83,11 @@ function validateParkedFolder(status, isInParked) {
72
83
  /**
73
84
  * Validate state without path information.
74
85
  */
75
- function validateStateOnly(status, criteria) {
86
+ function validateStateOnly(status, markdown, criteria) {
76
87
  if (status === "completed" && !criteria.allChecked) {
77
88
  return { valid: false, error: "Plan completed but criteria not met" };
78
89
  }
79
- if (status === "draft" && criteria.checked > 0) {
90
+ if (status === "draft" && countUnexplainedCheckedCriteria(markdown) > 0) {
80
91
  return { valid: false, error: "Plan draft but criteria checked" };
81
92
  }
82
93
  return { valid: true };
@@ -84,14 +95,14 @@ function validateStateOnly(status, criteria) {
84
95
  /**
85
96
  * Validate state with path information.
86
97
  */
87
- function validateStateWithPath(status, filePath, criteria) {
98
+ function validateStateWithPath(status, filePath, markdown, criteria) {
88
99
  const isInCompleted = filePath.includes("/completed/") || filePath.includes("completed/");
89
100
  const isInDraft = filePath.includes("/draft/") || filePath.includes("draft/");
90
101
  const isInParked = filePath.includes("/parked/") || filePath.includes("parked/");
91
102
  const completedResult = validateCompletedFolder(status, isInCompleted, criteria);
92
103
  if (!completedResult.valid)
93
104
  return completedResult;
94
- const draftResult = validateDraftFolder(status, isInDraft, criteria);
105
+ const draftResult = validateDraftFolder(status, isInDraft, markdown);
95
106
  if (!draftResult.valid)
96
107
  return draftResult;
97
108
  const parkedResult = validateParkedFolder(status, isInParked);
@@ -116,7 +127,7 @@ export function validatePlanState(markdown, filePath) {
116
127
  }
117
128
  const criteria = checkAcceptanceCriteria(markdown);
118
129
  if (!filePath) {
119
- return validateStateOnly(status, criteria);
130
+ return validateStateOnly(status, markdown, criteria);
120
131
  }
121
- return validateStateWithPath(status, filePath, criteria);
132
+ return validateStateWithPath(status, filePath, markdown, criteria);
122
133
  }
@@ -4,8 +4,10 @@ import { findPackageAsset } from "#utils/package-assets.js";
4
4
  const PACKAGED_MIGRATIONS_RELATIVE_DIR = "dist/esm/blueprint/db/migrations";
5
5
  const SOURCE_MIGRATIONS_RELATIVE_DIR = "src/blueprint/db/migrations";
6
6
  function resolveMigrationsDir() {
7
- const found = findPackageAsset(PACKAGED_MIGRATIONS_RELATIVE_DIR) ??
8
- findPackageAsset(SOURCE_MIGRATIONS_RELATIVE_DIR);
7
+ // Source checkouts may rebuild dist concurrently in another test project.
8
+ // Prefer immutable authoring SQL there; published packages fall back to dist.
9
+ const found = findPackageAsset(SOURCE_MIGRATIONS_RELATIVE_DIR) ??
10
+ findPackageAsset(PACKAGED_MIGRATIONS_RELATIVE_DIR);
9
11
  if (found)
10
12
  return found;
11
13
  throw new Error(`Missing blueprint migrations at ${PACKAGED_MIGRATIONS_RELATIVE_DIR} or ${SOURCE_MIGRATIONS_RELATIVE_DIR}. ` +