@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.
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
@@ -1,3 +1,4 @@
1
+ import { type GuardOutcome } from "#hooks/shared/guard-outcome";
1
2
  export type LogStatus = "PASS" | "BLOCK" | "WARN" | "ERROR";
2
3
  export type ToolType = string;
3
4
  export interface LogEntry {
@@ -6,6 +7,10 @@ export interface LogEntry {
6
7
  tool: ToolType;
7
8
  failures?: string[];
8
9
  error?: string;
10
+ /** GUARD_* reason code for the decision (additive; present on BLOCK/WARN). */
11
+ code?: string;
12
+ /** Two-axis guard outcome for the decision (additive). */
13
+ outcome?: GuardOutcome;
9
14
  }
10
15
  export interface LogConfig {
11
16
  logDir: string;
@@ -20,6 +25,8 @@ export interface ParsedLogLine {
20
25
  target: string;
21
26
  failures?: string[];
22
27
  error?: string;
28
+ code?: string;
29
+ outcome?: GuardOutcome;
23
30
  }
24
31
  export declare function createLogConfig(): LogConfig;
25
32
  export declare function parseLogLine(line: string): ParsedLogLine | null;
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
+ import { GUARD_EFFECT, GUARD_INTENT } from "#hooks/shared/guard-outcome";
3
4
  import { redactText } from "#mcp/tools/_shared/redact.js";
4
5
  import { getSurfacePath } from "#paths/state-root.js";
5
6
  const DEFAULT_MAX_LINES = 250;
@@ -42,6 +43,8 @@ export function parseLogLine(line) {
42
43
  ? parsed.failures.filter((value) => typeof value === "string")
43
44
  : undefined,
44
45
  error: typeof parsed.error === "string" ? parsed.error : undefined,
46
+ code: typeof parsed.code === "string" ? parsed.code : undefined,
47
+ outcome: isGuardOutcome(parsed.outcome) ? parsed.outcome : undefined,
45
48
  };
46
49
  }
47
50
  export function rotateLines(lines, maxLines) {
@@ -100,3 +103,16 @@ function isLogStatus(value) {
100
103
  function isToolType(value) {
101
104
  return typeof value === "string" && value.length > 0;
102
105
  }
106
+ const GUARD_EFFECT_VALUES = new Set(Object.values(GUARD_EFFECT));
107
+ const GUARD_INTENT_VALUES = new Set(Object.values(GUARD_INTENT));
108
+ function isGuardOutcome(value) {
109
+ if (typeof value !== "object" || value === null)
110
+ return false;
111
+ const record = value;
112
+ const effect = record["effect"];
113
+ const intent = record["intent"];
114
+ return (typeof effect === "string" &&
115
+ GUARD_EFFECT_VALUES.has(effect) &&
116
+ typeof intent === "string" &&
117
+ GUARD_INTENT_VALUES.has(intent));
118
+ }
@@ -10,6 +10,12 @@ export interface ProcessValidationOptions {
10
10
  readonly optimizationRules?: readonly OptimizationRule[];
11
11
  readonly recordOptimization?: (record: OptimizationTelemetryRecord) => void;
12
12
  }
13
+ /**
14
+ * Registered GUARD_* codes the runner emits directly (beyond the dev-routing
15
+ * inventory and the per-validator invariant codes in GUARD_VALIDATOR_CODES).
16
+ * Consumed by the guard-outcome registry-completeness test.
17
+ */
18
+ export declare const RUNNER_GUARD_CODES: readonly ["GUARD_PROMPT_INTENT"];
13
19
  export declare function runAllValidators(input: ToolInput): AggregateResult;
14
20
  export declare function formatOutput(aggregate: AggregateResult, input: ToolInput): void;
15
21
  export declare function getToolType(input: ToolInput): string;
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { getActivePromptRouting, isGuardEnabled } from "#hooks/guard-switch/state";
3
+ import { formatGuardReason, GUARD_EFFECT, GUARD_INTENT, GUARD_VALIDATOR_CODES, outcomeForCode, validatorUnexpectedCode, } from "#hooks/shared/guard-outcome";
3
4
  import { routePromptIntent } from "#hooks/prompt-routing";
4
- import { readStdinJson, suppressStderr, writeHookOutput } from "#hooks/shared/hook-bootstrap";
5
+ import { readStdinJson, suppressDecorativeStderr, writeGuardStderr, writeHookOutput, } from "#hooks/shared/hook-bootstrap";
5
6
  import { isSyntheticProcessExit } from "#hooks/shared/synthetic-process-exit.js";
6
7
  import { buildDenyEnvelope, buildUpdatedInputEnvelope, cloneToolInputWithCommand, getCommand, getFilePath, isBashInput, parseToolInput, } from "#hooks/shared/types";
7
8
  import { logRun } from "./logger.js";
@@ -15,6 +16,22 @@ const YELLOW = "\x1b[33m";
15
16
  const DIM = "\x1b[2m";
16
17
  const BOLD = "\x1b[1m";
17
18
  const NC = "\x1b[0m";
19
+ /** Code the pretool-guard emits when a routed prompt-intent forces a redirect. */
20
+ const PROMPT_INTENT_CODE = "GUARD_PROMPT_INTENT";
21
+ /**
22
+ * Registered GUARD_* codes the runner emits directly (beyond the dev-routing
23
+ * inventory and the per-validator invariant codes in GUARD_VALIDATOR_CODES).
24
+ * Consumed by the guard-outcome registry-completeness test.
25
+ */
26
+ export const RUNNER_GUARD_CODES = [
27
+ PROMPT_INTENT_CODE,
28
+ ];
29
+ /** Resolve the GUARD_* code for a failing validator, with a per-validator fallback. */
30
+ function codeForValidator(validatorName) {
31
+ if (!validatorName)
32
+ return "GUARD_VALIDATION_UNEXPECTED";
33
+ return GUARD_VALIDATOR_CODES[validatorName] ?? validatorUnexpectedCode(validatorName);
34
+ }
18
35
  export function runAllValidators(input) {
19
36
  const results = VALIDATORS.map((v) => v(input));
20
37
  const failed = results.filter((r) => !r.passed);
@@ -62,20 +79,35 @@ export function getTarget(input) {
62
79
  export function logValidationResult(result, target, tool) {
63
80
  if (!result.passed) {
64
81
  const failed = result.results.filter((r) => !r.passed);
82
+ const code = codeForValidator(failed[0]?.validator);
65
83
  logRun({
66
84
  status: "BLOCK",
67
85
  target: target.slice(0, 100),
68
86
  tool,
69
87
  failures: failed.map((f) => f.validator),
88
+ code,
89
+ outcome: outcomeForCode(code),
70
90
  });
71
91
  return;
72
92
  }
73
93
  const warnings = result.results.filter((r) => r.passed && r.message);
94
+ if (warnings.length > 0) {
95
+ // A warn does not reach the wire; it is advisory guidance in logs only.
96
+ const code = codeForValidator(warnings[0]?.validator);
97
+ logRun({
98
+ status: "WARN",
99
+ target: target.slice(0, 100),
100
+ tool,
101
+ failures: warnings.map((w) => w.validator),
102
+ code,
103
+ outcome: { effect: GUARD_EFFECT.allow, intent: GUARD_INTENT.advise },
104
+ });
105
+ return;
106
+ }
74
107
  logRun({
75
- status: warnings.length > 0 ? "WARN" : "PASS",
108
+ status: "PASS",
76
109
  target: target.slice(0, 100),
77
110
  tool,
78
- failures: warnings.length > 0 ? warnings.map((w) => w.validator) : undefined,
79
111
  });
80
112
  }
81
113
  export function handleParseError(error, inputJson) {
@@ -87,18 +119,20 @@ export function handleParseError(error, inputJson) {
87
119
  });
88
120
  console.error(`${RED}❌ Pretool Guard: Error parsing input${NC}`);
89
121
  console.error(`${RED} ${error instanceof Error ? error.message : "Unknown error"}${NC}`);
122
+ // Fail-closed as a CODED deny envelope so malformed/unhandled input still
123
+ // carries the stable GUARD_PRETOOL_FAILURE code on the wire, not a bare exit 2.
124
+ const detail = error instanceof Error ? error.message : "unknown error";
125
+ writeDenyDecision(formatGuardReason("GUARD_PRETOOL_FAILURE", `pretool-guard could not evaluate tool input: ${detail}`));
90
126
  process.exit(2);
91
127
  }
92
128
  function writeDenyDecision(permissionDecisionReason) {
129
+ // Deliver the coded reason on BOTH host channels: the stdout deny envelope
130
+ // (Codex reads hookSpecificOutput; Grok reads the dual top-level decision) AND
131
+ // the REAL stderr via writeGuardStderr (Claude reads stderr as the blocking
132
+ // feedback on an exit-2 PreToolUse deny — it ignores stdout there). The
133
+ // decorative diagnostics that go through console.error stay suppressed.
93
134
  writeHookOutput(JSON.stringify(buildDenyEnvelope({ reason: permissionDecisionReason })));
94
- // Claude/Codex treat exit 2 as deny and read the reason from stderr when present.
95
- // suppressStderr may null out fd 2; dual JSON still carries the reason for Grok.
96
- try {
97
- process.stderr.write(`${permissionDecisionReason}\n`);
98
- }
99
- catch {
100
- // ignore
101
- }
135
+ writeGuardStderr(`${permissionDecisionReason}\n`);
102
136
  }
103
137
  function writeUpdatedInput(input, command) {
104
138
  writeHookOutput(JSON.stringify(buildUpdatedInputEnvelope(input, command)));
@@ -125,12 +159,16 @@ function evaluateRoutingPolicy(input) {
125
159
  return {
126
160
  snapshot: { promptIntentReason, toolInputAction, commandActions: [] },
127
161
  denyReason: promptIntentReason,
162
+ code: PROMPT_INTENT_CODE,
163
+ outcome: outcomeForCode(PROMPT_INTENT_CODE),
128
164
  };
129
165
  }
130
166
  if (toolInputAction?.action === "deny" || toolInputAction?.action === "sandbox") {
131
167
  return {
132
168
  snapshot: { promptIntentReason, toolInputAction, commandActions },
133
169
  denyReason: toolInputAction.guidance,
170
+ code: toolInputAction.code,
171
+ outcome: outcomeForCode(toolInputAction.code),
134
172
  };
135
173
  }
136
174
  for (const commandAction of commandActions) {
@@ -138,18 +176,24 @@ function evaluateRoutingPolicy(input) {
138
176
  return {
139
177
  snapshot: { promptIntentReason, toolInputAction, commandActions },
140
178
  denyReason: commandAction.action.guidance,
179
+ code: commandAction.action.code,
180
+ outcome: outcomeForCode(commandAction.action.code),
141
181
  };
142
182
  }
143
183
  if (commandAction.action?.action === "sandbox" && !commandAction.alreadySandboxed) {
144
184
  return {
145
185
  snapshot: { promptIntentReason, toolInputAction, commandActions },
146
186
  denyReason: commandAction.action.guidance,
187
+ code: commandAction.action.code,
188
+ outcome: outcomeForCode(commandAction.action.code),
147
189
  };
148
190
  }
149
191
  }
150
192
  return {
151
193
  snapshot: { promptIntentReason, toolInputAction, commandActions },
152
194
  denyReason: null,
195
+ code: null,
196
+ outcome: null,
153
197
  };
154
198
  }
155
199
  function policySnapshotsMatch(originalRouting, replacementRouting, originalValidation, replacementValidation) {
@@ -182,8 +226,11 @@ export function processValidation(inputJson, options = {}) {
182
226
  const input = parseToolInput(inputJson);
183
227
  const command = isBashInput(input) ? getCommand(input) : null;
184
228
  const originalRouting = evaluateRoutingPolicy(input);
185
- if (originalRouting.denyReason !== null) {
186
- writeDenyDecision(originalRouting.denyReason);
229
+ if (originalRouting.denyReason !== null && originalRouting.code !== null) {
230
+ // formatGuardReason owns the 80-char budget on BOTH deny paths (this routing
231
+ // path was previously untruncated) and rides the GUARD_* code inside the
232
+ // host-displayed reason so consumers key on the code, not on wording.
233
+ writeDenyDecision(formatGuardReason(originalRouting.code, originalRouting.denyReason));
187
234
  // Exit 2 so Grok (and other exit-code runners) treat this as an explicit
188
235
  // deny; dual JSON still carries Claude/Codex hookSpecificOutput.
189
236
  process.exit(2);
@@ -201,8 +248,11 @@ export function processValidation(inputJson, options = {}) {
201
248
  .map((r) => r.message)
202
249
  .find((message) => typeof message === "string" && message.length > 0);
203
250
  const validatorReason = failed.map((r) => r.validator).join(", ");
204
- const reason = (messageReason ?? (validatorReason || "pretool-guard validation failed")).slice(0, 80);
205
- writeDenyDecision(reason);
251
+ const rawReason = messageReason ?? (validatorReason || "pretool-guard validation failed");
252
+ // Code derives from the first failing validator's invariant, with a
253
+ // per-validator GUARD_<NAME>_UNEXPECTED fallback for unclassified failures.
254
+ const code = codeForValidator(failed[0]?.validator);
255
+ writeDenyDecision(formatGuardReason(code, rawReason));
206
256
  process.exit(2);
207
257
  }
208
258
  if (command) {
@@ -235,7 +285,9 @@ export function processValidation(inputJson, options = {}) {
235
285
  process.exit(result.exitCode);
236
286
  }
237
287
  export async function main() {
238
- suppressStderr();
288
+ // Suppress decorative stderr but keep fd 2 open so a deny can deliver its
289
+ // coded reason to the real stderr (Claude's exit-2 blocking-feedback channel).
290
+ suppressDecorativeStderr();
239
291
  const inputJson = await readStdinJson();
240
292
  if (!inputJson.trim()) {
241
293
  writeHookOutput("{}\n");
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { getFilePath, isFileEditInput, isFileWriteInput } from "#hooks/shared/types";
3
3
  import { validateBlueprint as validateBlueprintShared } from "#hooks/shared/validators/blueprint";
4
- import { isCanonicalBlueprintDocumentPath, isCanonicalBlueprintReviewLedgerPath, } from "./path-contract.js";
4
+ import { getCanonicalBlueprintDocument, getCanonicalBlueprintReviewLedgerPath, } from "./path-contract.js";
5
5
  import { buildRedirectMessage } from "./mcp-redirect.js";
6
6
  function normalizeFilePath(filePath, cwd) {
7
7
  if (!path.isAbsolute(filePath))
@@ -10,6 +10,23 @@ function normalizeFilePath(filePath, cwd) {
10
10
  return filePath.replace(/^\/+/, "");
11
11
  return path.relative(cwd, filePath).replace(/\\/g, "/");
12
12
  }
13
+ /**
14
+ * Build a short, deterministic `<state>/<slug>` display identity from the
15
+ * PARSED match, never from the raw input path. The raw path is a
16
+ * cwd-relative string that can legitimately climb several parent-directory
17
+ * segments out of the effective repo root (e.g. into
18
+ * `.webpresso/worktrees/repos/<hash>/.../_overview.md` when cwd is the
19
+ * primary checkout but the edited file lives inside a managed blueprint
20
+ * worktree) — confusing to read and, worse, long enough to consume
21
+ * the guard's entire wire-message budget (`GUARD_REASON_BUDGET`, 80 Unicode
22
+ * code points total, see `#hooks/shared/guard-outcome.ts`) before any
23
+ * actionable "use this MCP tool" guidance is shown. Dropping the root prefix
24
+ * and document filename (both implied by the "blueprint" category, not
25
+ * distinguishing information) buys back budget for that guidance.
26
+ */
27
+ function canonicalDisplayId(state, slug) {
28
+ return `${state}/${slug}`;
29
+ }
13
30
  export function validateBlueprint(input) {
14
31
  const filePath = getFilePath(input);
15
32
  if (filePath && (isFileWriteInput(input) || isFileEditInput(input))) {
@@ -17,28 +34,31 @@ export function validateBlueprint(input) {
17
34
  // Detect a canonical blueprint document from EITHER the cwd-normalized form
18
35
  // OR the raw path. Detection is prefix-agnostic (path-contract), so an
19
36
  // absolute worktree path is caught even when input.cwd is undefined or does
20
- // not reduce the path to a recognized blueprints root.
21
- if (isCanonicalBlueprintDocumentPath(normalized) ||
22
- isCanonicalBlueprintDocumentPath(filePath)) {
37
+ // not reduce the path to a recognized blueprints root — including when cwd
38
+ // points at an unrelated checkout (e.g. the primary checkout) and the file
39
+ // lives inside a managed worktree.
40
+ const canonicalDoc = getCanonicalBlueprintDocument(normalized) ?? getCanonicalBlueprintDocument(filePath);
41
+ if (canonicalDoc) {
23
42
  return {
24
43
  validator: "blueprint",
25
44
  passed: false,
26
45
  message: buildRedirectMessage({
27
46
  category: "blueprint",
28
- command: normalized,
29
- fallbackHint: "wp_blueprint_create / wp_blueprint_put / wp_blueprint_transition / wp_blueprint_task_verify",
47
+ command: canonicalDisplayId(canonicalDoc.state, canonicalDoc.slug),
48
+ fallbackHint: "wp_blueprint_put",
30
49
  }),
31
50
  };
32
51
  }
33
- if (isCanonicalBlueprintReviewLedgerPath(normalized) ||
34
- isCanonicalBlueprintReviewLedgerPath(filePath)) {
52
+ const ledgerDoc = getCanonicalBlueprintReviewLedgerPath(normalized) ??
53
+ getCanonicalBlueprintReviewLedgerPath(filePath);
54
+ if (ledgerDoc) {
35
55
  return {
36
56
  validator: "blueprint",
37
57
  passed: false,
38
58
  message: buildRedirectMessage({
39
59
  category: "blueprint",
40
- command: normalized,
41
- fallbackHint: "wp_blueprint_review_log (or wp review log --reviewer <id>)",
60
+ command: canonicalDisplayId(ledgerDoc.state, ledgerDoc.slug),
61
+ fallbackHint: "wp_blueprint_review_log",
42
62
  }),
43
63
  };
44
64
  }
@@ -348,6 +348,7 @@ export function createAuditResult(command, rule, options = {}) {
348
348
  }
349
349
  const AUDIT_KIND_SET = new Set(MCP_AUDIT_KINDS);
350
350
  const WP_AUDIT_RE = /^wp\s+audit\s+([a-z0-9-]+)\b/u;
351
+ const WP_CI_PREFLIGHT_RE = /^wp\s+ci-preflight\b/u;
351
352
  const SCRIPT_INVOCATION_RE = /^(?:pnpm run|vp run|npm run|pnpm|npm)\s+([A-Za-z0-9:_-]+)/u;
352
353
  const RAW_PM_RE = /^(?:pnpm|npm)\b/u;
353
354
  function loadGuardConfig(projectDir) {
@@ -389,6 +390,53 @@ function findWpAuditRedirect(command) {
389
390
  }
390
391
  return undefined;
391
392
  }
393
+ /** `wp ci-preflight` (CLI) → `wp_ci_preflight` MCP tool. 1:1 mapping, not gated on config. */
394
+ function findWpCiPreflightRedirect(command) {
395
+ for (const variant of getCommandVariants(command)) {
396
+ if (WP_CI_PREFLIGHT_RE.test(variant)) {
397
+ return `"${variant}" denied — use the webpresso MCP tool wp_ci_preflight(). Runs the same fail-fast, branch-scoped pre-push gate (format → guardrails → typecheck → lint → test) without shelling out. Returns structured, summary-first results.`;
398
+ }
399
+ }
400
+ return undefined;
401
+ }
402
+ const OPENCODE_RUN_RE = /^opencode\s+run\b/u;
403
+ const CODEX_EXEC_OR_REVIEW_RE = /^codex\s+(?:exec|review)\b/u;
404
+ /**
405
+ * Raw provider-CLI outside-voice review invocations (`opencode run ...`,
406
+ * `codex exec ...`, `codex review`) → `wp_review_run`. Generic; not gated on
407
+ * config, mirroring `findWpAuditRedirect`.
408
+ *
409
+ * Deliberately narrow: only the review-launching subcommands are blocked.
410
+ * `codex login status`, `codex --version`, `opencode models opencode-go`
411
+ * (listing available models — documented in the `codex` skill), and
412
+ * `opencode --version` are legitimate, non-review invocations and must stay
413
+ * unblocked.
414
+ *
415
+ * A single tool exists here (not a category with several natural
416
+ * `wp_<suffix>` matchers), so this deliberately does NOT go through the
417
+ * generic `CommandRule`/`buildRedirectMessage` category pipeline — that
418
+ * machinery always derives its "use the webpresso MCP tool" claim from a
419
+ * category → tool-suffix map, which is correct for lint/test/typecheck
420
+ * (real per-category MCP tools) but would misname the single review tool.
421
+ * Mirrors `findWpAuditRedirect`'s unconditional standalone-message shape:
422
+ * always names `wp_review_run` directly rather than gating on
423
+ * `resolveMcpReady` (the fallback tier that message implies — the `wp
424
+ * review run` CLI documented in the `codex` skill — is unconditionally
425
+ * still available as a manual fallback when MCP genuinely is not ready).
426
+ */
427
+ function findReviewCliRedirect(command) {
428
+ for (const variant of getCommandVariants(command)) {
429
+ if (!OPENCODE_RUN_RE.test(variant) && !CODEX_EXEC_OR_REVIEW_RE.test(variant))
430
+ continue;
431
+ return [
432
+ `"${variant}" denied — use the webpresso MCP tool: wp_review_run(...).`,
433
+ "It runs the probe + review stages of an outside-voice provider review in one call and returns structured, summary-first results with artifact capture under .webpresso/reviews.",
434
+ "Provider fallback (e.g. Codex out of API credits) is a `provider` PARAMETER change on that SAME tool call (claude|codex|opencode|grok) — never shell into a different provider's raw CLI.",
435
+ "Fallback if MCP is genuinely unavailable: the `wp review run` CLI (see the `codex` skill) — never a different provider's raw CLI.",
436
+ ].join("\n");
437
+ }
438
+ return undefined;
439
+ }
392
440
  /** Repo-declared `guard.scriptRoutes`: a package script mapped to an audit kind. */
393
441
  function findScriptRouteRedirect(command, routes) {
394
442
  for (const variant of getCommandVariants(command)) {
@@ -446,6 +494,12 @@ export function validateForbiddenCommands(input) {
446
494
  const wpAuditRedirect = findWpAuditRedirect(command);
447
495
  if (wpAuditRedirect)
448
496
  return guardRedirect(wpAuditRedirect);
497
+ const wpCiPreflightRedirect = findWpCiPreflightRedirect(command);
498
+ if (wpCiPreflightRedirect)
499
+ return guardRedirect(wpCiPreflightRedirect);
500
+ const reviewCliRedirect = findReviewCliRedirect(command);
501
+ if (reviewCliRedirect)
502
+ return guardRedirect(reviewCliRedirect);
449
503
  const guard = loadGuardConfig(projectDir);
450
504
  if (guard?.scriptRoutes) {
451
505
  const redirect = findScriptRouteRedirect(command, guard.scriptRoutes);
@@ -12,4 +12,11 @@ import { validateTestQuality } from "./test-quality.js";
12
12
  import { validateUxQuality } from "./ux-quality.js";
13
13
  import { validateWorktreeDiscipline } from "./worktree-discipline.js";
14
14
  export declare const VALIDATORS: (typeof validateBlueprintFirst | typeof validateDocsGovernance | typeof validatePackageImports)[];
15
+ /**
16
+ * Canonical `validator` name each entry in {@link VALIDATORS} reports on its
17
+ * `ValidationResult`. Kept in lockstep with the array above; the guard-outcome
18
+ * registry-completeness test asserts every name has a GUARD_* invariant code
19
+ * (and vice versa).
20
+ */
21
+ export declare const VALIDATOR_NAMES: readonly ["forbidden-commands", "dangerous-commands", "blueprint", "blueprint-first", "docs-governance", "plan-frontmatter", "complexity", "package-imports", "file-conventions", "command-file", "test-quality", "ux-quality", "worktree-discipline"];
15
22
  export { validateBlueprint, validateBlueprintFirst, validateCommandFile, validateComplexity, validateDangerousCommands, validateDocsGovernance, validateFileConventions, validateForbiddenCommands, validatePackageImports, validatePlanFrontmatter, validateTestQuality, validateUxQuality, validateWorktreeDiscipline, };
@@ -26,4 +26,25 @@ export const VALIDATORS = [
26
26
  validateUxQuality,
27
27
  validateWorktreeDiscipline,
28
28
  ];
29
+ /**
30
+ * Canonical `validator` name each entry in {@link VALIDATORS} reports on its
31
+ * `ValidationResult`. Kept in lockstep with the array above; the guard-outcome
32
+ * registry-completeness test asserts every name has a GUARD_* invariant code
33
+ * (and vice versa).
34
+ */
35
+ export const VALIDATOR_NAMES = [
36
+ "forbidden-commands",
37
+ "dangerous-commands",
38
+ "blueprint",
39
+ "blueprint-first",
40
+ "docs-governance",
41
+ "plan-frontmatter",
42
+ "complexity",
43
+ "package-imports",
44
+ "file-conventions",
45
+ "command-file",
46
+ "test-quality",
47
+ "ux-quality",
48
+ "worktree-discipline",
49
+ ];
29
50
  export { validateBlueprint, validateBlueprintFirst, validateCommandFile, validateComplexity, validateDangerousCommands, validateDocsGovernance, validateFileConventions, validateForbiddenCommands, validatePackageImports, validatePlanFrontmatter, validateTestQuality, validateUxQuality, validateWorktreeDiscipline, };
@@ -1,3 +1,4 @@
1
+ import type { BlueprintDocumentPath, BlueprintStatus } from "#utils/document-paths.js";
1
2
  export declare const BLUEPRINTS_ROOT = "webpresso/blueprints";
2
3
  export declare const TECH_DEBT_ROOT = "webpresso/tech-debt";
3
4
  /**
@@ -12,5 +13,23 @@ export declare function getNonCanonicalPlanningPathViolation(filePath: string, b
12
13
  */
13
14
  export declare function isCanonicalBlueprintOverviewPath(filePath: string, blueprintsRoot?: string): boolean;
14
15
  export declare function isCanonicalBlueprintDocumentPath(filePath: string, blueprintsRoot?: string): boolean;
16
+ /**
17
+ * Same detection as {@link isCanonicalBlueprintDocumentPath}, but returns the
18
+ * parsed `<state>/<slug>/<doc>` identity instead of a boolean. Callers that
19
+ * need to DISPLAY the match (e.g. a guard denial message) should build their
20
+ * text from this clean, root-relative identity rather than from the raw
21
+ * input path — a raw path can be an arbitrarily long, cwd-relative string
22
+ * (e.g. several parent-directory segments climbing out of an unrelated
23
+ * worktree) that is both confusing and liable to consume an entire
24
+ * wire-message budget before any actionable guidance is shown.
25
+ */
26
+ export declare function getCanonicalBlueprintDocument(filePath: string, blueprintsRoot?: string): BlueprintDocumentPath | null;
27
+ export interface ReviewLedgerDocumentPath {
28
+ state: BlueprintStatus;
29
+ slug: string;
30
+ doc: "reviews.md" | "review-events.jsonl";
31
+ }
32
+ /** Same detection as {@link isCanonicalBlueprintReviewLedgerPath}, returning the parsed identity. */
33
+ export declare function getCanonicalBlueprintReviewLedgerPath(filePath: string, blueprintsRoot?: string): ReviewLedgerDocumentPath | null;
15
34
  export declare function isCanonicalBlueprintReviewLedgerPath(filePath: string, blueprintsRoot?: string): boolean;
16
35
  export declare function getBlueprintPathViolation(filePath: string, blueprintsRoot?: string, cwd?: string): string | null;
@@ -107,7 +107,17 @@ function findCanonicalBlueprintDocument(normalized, roots) {
107
107
  }
108
108
  return null;
109
109
  }
110
- function getCanonicalBlueprintDocument(filePath, blueprintsRoot) {
110
+ /**
111
+ * Same detection as {@link isCanonicalBlueprintDocumentPath}, but returns the
112
+ * parsed `<state>/<slug>/<doc>` identity instead of a boolean. Callers that
113
+ * need to DISPLAY the match (e.g. a guard denial message) should build their
114
+ * text from this clean, root-relative identity rather than from the raw
115
+ * input path — a raw path can be an arbitrarily long, cwd-relative string
116
+ * (e.g. several parent-directory segments climbing out of an unrelated
117
+ * worktree) that is both confusing and liable to consume an entire
118
+ * wire-message budget before any actionable guidance is shown.
119
+ */
120
+ export function getCanonicalBlueprintDocument(filePath, blueprintsRoot) {
111
121
  const normalized = normalizePlanningPath(filePath);
112
122
  const roots = blueprintsRoot ? [blueprintsRoot] : CANONICAL_BLUEPRINTS_ROOTS;
113
123
  return findCanonicalBlueprintDocument(normalized, roots);
@@ -115,12 +125,14 @@ function getCanonicalBlueprintDocument(filePath, blueprintsRoot) {
115
125
  function parseReviewLedgerRelativePath(tail) {
116
126
  const parts = tail.split("/").filter((segment) => segment.length > 0);
117
127
  if (parts.length < 3)
118
- return false;
128
+ return null;
119
129
  const [state, slug, ...rest] = parts;
120
130
  if (!isBlueprintStatus(state) || !slug)
121
- return false;
131
+ return null;
122
132
  const doc = rest.join("/");
123
- return doc === "reviews.md" || doc === "review-events.jsonl";
133
+ if (doc !== "reviews.md" && doc !== "review-events.jsonl")
134
+ return null;
135
+ return { state, slug, doc };
124
136
  }
125
137
  /**
126
138
  * Prefix-agnostic detection of a blueprint's review-ledger AUTHORITY files:
@@ -140,17 +152,22 @@ function findCanonicalBlueprintReviewLedgerPath(normalized, roots) {
140
152
  if (!matchesHere)
141
153
  continue;
142
154
  const tail = segments.slice(start + rootSegments.length).join("/");
143
- if (parseReviewLedgerRelativePath(tail))
144
- return true;
155
+ const parsed = parseReviewLedgerRelativePath(tail);
156
+ if (parsed)
157
+ return parsed;
145
158
  }
146
159
  }
147
- return false;
160
+ return null;
148
161
  }
149
- export function isCanonicalBlueprintReviewLedgerPath(filePath, blueprintsRoot) {
162
+ /** Same detection as {@link isCanonicalBlueprintReviewLedgerPath}, returning the parsed identity. */
163
+ export function getCanonicalBlueprintReviewLedgerPath(filePath, blueprintsRoot) {
150
164
  const normalized = normalizePlanningPath(filePath);
151
165
  const roots = blueprintsRoot ? [blueprintsRoot] : CANONICAL_BLUEPRINTS_ROOTS;
152
166
  return findCanonicalBlueprintReviewLedgerPath(normalized, roots);
153
167
  }
168
+ export function isCanonicalBlueprintReviewLedgerPath(filePath, blueprintsRoot) {
169
+ return getCanonicalBlueprintReviewLedgerPath(filePath, blueprintsRoot) !== null;
170
+ }
154
171
  export function getBlueprintPathViolation(filePath, blueprintsRoot, cwd = process.cwd()) {
155
172
  const normalized = normalizePlanningPath(filePath);
156
173
  if (!isBlueprintPath(normalized, blueprintsRoot))
@@ -95,7 +95,12 @@ export function validateWorktreeDiscipline(input) {
95
95
  return { validator: VALIDATOR_NAME, passed: true };
96
96
  }
97
97
  function normalizedToolName(input) {
98
- return (input.tool_name ?? "").toLowerCase().replaceAll(/[^a-z]/gu, "");
98
+ // tool_name is typed string but malformed host envelopes can deliver non-strings;
99
+ // never call string methods without a typeof guard (same class as isBashInput).
100
+ const raw = input.tool_name;
101
+ if (typeof raw !== "string")
102
+ return "";
103
+ return raw.toLowerCase().replaceAll(/[^a-z]/gu, "");
99
104
  }
100
105
  function applyPatchTargets(input) {
101
106
  if (!normalizedToolName(input).endsWith("applypatch"))