failproofai 0.0.14-beta.2 → 0.0.15-beta.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 (269) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +6 -6
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +2 -2
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  13. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  18. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  20. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  26. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  27. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  29. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/invite/route.js +1 -1
  31. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
  33. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/logout/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +1 -1
  41. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/status/route.js +1 -1
  43. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  45. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  47. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  49. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/index.html +1 -1
  52. package/.next/standalone/.next/server/app/index.rsc +15 -15
  53. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  54. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  55. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  56. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  57. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  58. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  59. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/page.js +2 -2
  61. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  64. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  65. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  66. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  67. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  69. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  70. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -4
  71. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  75. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  76. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -4
  77. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  80. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  81. package/.next/standalone/.next/server/app/projects/page.js +3 -3
  82. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0hv08mx._.js → [root-of-the-server]__0-dh5jy._.js} +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0obaznk._.js → [root-of-the-server]__0h3lfmp._.js} +1 -1
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0xvsnvt._.js → [root-of-the-server]__0l4vuqm._.js} +1 -1
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sb_5m8._.js +4 -4
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xuaoik._.js +6 -4
  94. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1hkdkmz._.js → [root-of-the-server]__10oa-v9._.js} +1 -1
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1i9rknc._.js → [root-of-the-server]__18d-9oi._.js} +1 -1
  97. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1is3glr._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +6 -6
  102. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0hdsupo._.js → [root-of-the-server]__0114ewg._.js} +3 -3
  106. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01od0m6._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0s89hoe._.js → [root-of-the-server]__02r5bgf._.js} +3 -3
  108. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__09v9cwb._.js → [root-of-the-server]__04o-kw7._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qeuy9c._.js → [root-of-the-server]__0e8sjqm._.js} +3 -3
  110. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1tkjqka._.js → [root-of-the-server]__0lkzqax._.js} +3 -3
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lpvwqm._.js +21 -0
  112. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ynf7tx._.js → [root-of-the-server]__0qucxyj._.js} +3 -3
  113. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__03lv-pe._.js → [root-of-the-server]__0u02miy._.js} +3 -3
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +8 -8
  116. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00cz9as._.js → [root-of-the-server]__1apb7k_._.js} +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +4 -0
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1i21qhv._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1py795m._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__015_i4t._.js → [root-of-the-server]__1zqz4v8._.js} +3 -3
  123. package/.next/standalone/.next/server/chunks/ssr/_077y5np._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/_1-3unkg._.js +3 -0
  125. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/app_audit_loading_tsx_1j1kc6j._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  132. package/.next/standalone/.next/server/chunks/ssr/lib_0xkhw_v._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/lib_hermes-projects_ts_05b8-it._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
  135. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_06xsmte.js → node_modules_html-to-image_es_index_0w82veb.js} +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  137. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  138. package/.next/standalone/.next/server/pages/404.html +1 -1
  139. package/.next/standalone/.next/server/pages/500.html +1 -1
  140. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  141. package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
  142. package/.next/standalone/.next/static/chunks/0hn30_j22wpp_.js +6 -0
  143. package/.next/standalone/.next/static/chunks/{2llkdafnaejw_.js → 1_20f96f1253b.js} +1 -1
  144. package/.next/standalone/.next/static/chunks/1hdhteui932bq.js +1 -0
  145. package/.next/standalone/.next/static/chunks/{33u59vf_8xpd-.js → 1j2xm011_aoae.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/{388hgfqye67xa.js → 1qzsbft-jtnf8.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/{3-ogr79xqbw0h.js → 2mazwb1qmur1p.js} +1 -1
  148. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +2 -0
  149. package/.next/standalone/.next/static/chunks/2xbkqlma_-61k.js +1 -0
  150. package/.next/standalone/.next/static/chunks/{29zkom4s9de2d.js → 2y7acsw0--8xf.js} +1 -1
  151. package/.next/standalone/.next/static/chunks/3060a-0fayz96.js +1 -0
  152. package/.next/standalone/.next/static/chunks/312ihvhxeuoi_.js +1 -0
  153. package/.next/standalone/.next/static/chunks/31c084gsop1bb.js +1 -0
  154. package/.next/standalone/.next/static/chunks/{1ua5armwfph8o.js → 31fmsp1soxi07.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{02jcotghcxrbu.js → 3kmwfieo15mr2.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{1gg-8e9q_m__9.js → 3q9y67-fobgpc.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{turbopack-10hj8t5pf33tu.js → turbopack-1lsv92x7klwrn.js} +1 -1
  158. package/.next/standalone/app/actions/get-hooks-config.ts +85 -0
  159. package/.next/standalone/app/components/log-viewer/entry-row.tsx +8 -3
  160. package/.next/standalone/app/components/project-list.tsx +229 -42
  161. package/.next/standalone/app/components/raw-log-viewer.tsx +33 -15
  162. package/.next/standalone/app/policies/hooks-client.tsx +113 -15
  163. package/.next/standalone/app/project/[name]/page.tsx +12 -2
  164. package/.next/standalone/integration-suite/README.md +43 -1
  165. package/.next/standalone/integration-suite/ci-entrypoint.sh +21 -6
  166. package/.next/standalone/integration-suite/install-clis.sh +58 -13
  167. package/.next/standalone/integration-suite/probe-cli.sh +51 -11
  168. package/.next/standalone/integration-suite/report.js +52 -6
  169. package/.next/standalone/integration-suite/run.sh +55 -3
  170. package/.next/standalone/lib/entry-keys.ts +36 -0
  171. package/.next/standalone/lib/hermes-profiles.ts +156 -0
  172. package/.next/standalone/lib/hermes-projects.ts +91 -25
  173. package/.next/standalone/lib/hermes-sessions.ts +65 -10
  174. package/.next/standalone/lib/openclaw-projects.ts +111 -18
  175. package/.next/standalone/lib/openclaw-sessions.ts +20 -0
  176. package/.next/standalone/lib/paths.ts +36 -0
  177. package/.next/standalone/lib/project-tree.ts +193 -0
  178. package/.next/standalone/lib/projects.ts +22 -0
  179. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  180. package/.next/standalone/node_modules/next/dist/build/next-config-ts/transpile-config.js +3 -114
  181. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  182. package/.next/standalone/node_modules/next/dist/cli/next-test.js +2 -1
  183. package/.next/standalone/node_modules/next/dist/compiled/http-proxy/index.js +5 -5
  184. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +11 -10
  185. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +11 -10
  186. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +2 -2
  187. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +9 -9
  188. package/.next/standalone/node_modules/next/dist/lib/install-dependencies.js +1 -1
  189. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  190. package/.next/standalone/node_modules/next/dist/lib/typescript/loadTsConfig.js +181 -0
  191. package/.next/standalone/node_modules/next/dist/lib/typescript/missingDependencyError.js +1 -1
  192. package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeCheckCli.js +53 -0
  193. package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeScriptCli.js +256 -0
  194. package/.next/standalone/node_modules/next/dist/lib/verify-typescript-setup.js +97 -56
  195. package/.next/standalone/node_modules/next/dist/server/app-render/action-handler.js +129 -34
  196. package/.next/standalone/node_modules/next/dist/server/app-render/manifests-singleton.js +52 -4
  197. package/.next/standalone/node_modules/next/dist/server/config-schema.js +1 -1
  198. package/.next/standalone/node_modules/next/dist/server/config-shared.js +1 -2
  199. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  200. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  201. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  202. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +8 -27
  203. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  204. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +90 -53
  205. package/.next/standalone/node_modules/next/dist/server/lib/patch-fetch.js +15 -2
  206. package/.next/standalone/node_modules/next/dist/server/lib/router-utils/proxy-request.js +4 -0
  207. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  208. package/.next/standalone/node_modules/next/dist/server/web/spec-extension/unstable-cache.js +1 -1
  209. package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  210. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/prepare-destination.js +2 -1
  211. package/.next/standalone/node_modules/next/dist/shared/lib/server-reference-info.js +12 -0
  212. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  213. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  214. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  215. package/.next/standalone/node_modules/next/package.json +10 -10
  216. package/.next/standalone/package.json +9 -7
  217. package/.next/standalone/pi-extension/index.ts +38 -5
  218. package/.next/standalone/server.js +1 -1
  219. package/dist/cli.mjs +623 -308
  220. package/lib/entry-keys.ts +36 -0
  221. package/lib/hermes-profiles.ts +156 -0
  222. package/lib/hermes-projects.ts +91 -25
  223. package/lib/hermes-sessions.ts +65 -10
  224. package/lib/openclaw-projects.ts +111 -18
  225. package/lib/openclaw-sessions.ts +20 -0
  226. package/lib/paths.ts +36 -0
  227. package/lib/project-tree.ts +193 -0
  228. package/lib/projects.ts +22 -0
  229. package/package.json +9 -7
  230. package/pi-extension/index.ts +38 -5
  231. package/scripts/launch.ts +7 -0
  232. package/src/audit/cli-adapters/hermes.ts +6 -3
  233. package/src/audit/cli-adapters/openclaw.ts +17 -13
  234. package/src/hooks/configure-wizard.ts +24 -2
  235. package/src/hooks/custom-hooks-loader.ts +58 -4
  236. package/src/hooks/enforcement-capability.ts +266 -0
  237. package/src/hooks/handler.ts +12 -1
  238. package/src/hooks/hook-activity-store.ts +14 -0
  239. package/src/hooks/hooks-config.ts +59 -1
  240. package/src/hooks/integrations.ts +123 -19
  241. package/src/hooks/manager.ts +93 -27
  242. package/src/hooks/policy-evaluator.ts +59 -7
  243. package/src/hooks/policy-types.ts +26 -0
  244. package/src/hooks/types.ts +50 -2
  245. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +0 -3
  246. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +0 -3
  247. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +0 -3
  248. package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +0 -3
  249. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +0 -3
  250. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +0 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +0 -3
  252. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0twi4fj._.js +0 -3
  253. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +0 -4
  254. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +0 -3
  255. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fax1sl._.js +0 -19
  256. package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +0 -3
  260. package/.next/standalone/.next/static/chunks/0h-6ymo9v86b0.js +0 -6
  261. package/.next/standalone/.next/static/chunks/0pndjhcxfu-0t.js +0 -1
  262. package/.next/standalone/.next/static/chunks/1-33f82_l32or.js +0 -1
  263. package/.next/standalone/.next/static/chunks/1bg_g3g359c_d.js +0 -1
  264. package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +0 -2
  265. package/.next/standalone/.next/static/chunks/396764jgd7-9j.js +0 -1
  266. package/.next/standalone/.next/static/chunks/3w7g0n0iwwnbs.js +0 -1
  267. /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_buildManifest.js +0 -0
  268. /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_clientMiddlewareManifest.js +0 -0
  269. /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_ssgManifest.js +0 -0
@@ -12,8 +12,10 @@ import { resolve, dirname } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { homedir } from "node:os";
14
14
  import { parseDocument, type Document } from "yaml";
15
+ import { listHermesProfiles, hermesRoot } from "../../lib/hermes-profiles";
15
16
  import {
16
17
  HOOK_EVENT_TYPES,
18
+ CLAUDE_INSTALL_EVENT_TYPES,
17
19
  HOOK_SCOPES,
18
20
  CODEX_HOOK_EVENT_TYPES,
19
21
  CODEX_HOOK_SCOPES,
@@ -118,6 +120,18 @@ export interface Integration {
118
120
  /** Resolve the per-scope settings/hooks file path. */
119
121
  getSettingsPath(scope: HookScope, cwd?: string): string;
120
122
 
123
+ /**
124
+ * Every settings file this integration must write for a scope.
125
+ *
126
+ * Optional — defaults to `[getSettingsPath(scope, cwd)]`, which is right for
127
+ * every CLI whose scope maps to exactly one file. Hermes overrides it because
128
+ * each of its PROFILES is a separate home dir with its own `config.yaml`:
129
+ * writing only `~/.hermes/config.yaml` would leave every other profile running
130
+ * unhooked, silently. Use `settingsPathsFor(integration, …)` at call sites
131
+ * rather than calling this directly.
132
+ */
133
+ getSettingsPaths?(scope: HookScope, cwd?: string): string[];
134
+
121
135
  /** Read the raw settings/hooks file (returns {} when missing). */
122
136
  readSettings(settingsPath: string): Record<string, unknown>;
123
137
 
@@ -143,13 +157,28 @@ export interface Integration {
143
157
  detectInstalled(): boolean;
144
158
  }
145
159
 
160
+ /**
161
+ * Every settings file to write/remove for `(integration, scope)`.
162
+ *
163
+ * Single-file integrations (all but Hermes) return exactly one path, so callers
164
+ * can loop unconditionally.
165
+ */
166
+ export function settingsPathsFor(
167
+ integration: Integration,
168
+ scope: HookScope,
169
+ cwd?: string,
170
+ ): string[] {
171
+ const paths = integration.getSettingsPaths?.(scope, cwd);
172
+ return paths && paths.length > 0 ? paths : [integration.getSettingsPath(scope, cwd)];
173
+ }
174
+
146
175
  // ── Claude Code integration ─────────────────────────────────────────────────
147
176
 
148
177
  export const claudeCode: Integration = {
149
178
  id: "claude",
150
179
  displayName: "Claude Code",
151
180
  scopes: HOOK_SCOPES,
152
- eventTypes: HOOK_EVENT_TYPES,
181
+ eventTypes: CLAUDE_INSTALL_EVENT_TYPES,
153
182
 
154
183
  getSettingsPath(scope, cwd) {
155
184
  const base = cwd ? resolve(cwd) : process.cwd();
@@ -196,7 +225,48 @@ export const claudeCode: Integration = {
196
225
  const s = settings as ClaudeSettings;
197
226
  if (!s.hooks) s.hooks = {};
198
227
 
199
- for (const eventType of HOOK_EVENT_TYPES) {
228
+ // Drop our hook from any event we no longer install. Without this, an
229
+ // event removed from the list stays in settings.json forever on existing
230
+ // machines — reinstalling would not repair it — which is exactly the
231
+ // situation `WorktreeCreate` created: registered, silent on allow, and
232
+ // therefore breaking `claude --worktree` until hand-edited. Only OUR
233
+ // marked entries are touched; anyone else's hooks on the same event stay.
234
+ //
235
+ // This walks EVERY key the file happens to carry, including ones
236
+ // failproofai has never written — a newer Claude event, another tool's
237
+ // entry, a typo. Their values are therefore unvalidated input, and a
238
+ // non-array one (`"Foo": {}` or `"Foo": "bar"`) makes the iteration below
239
+ // throw. That aborts the whole install AFTER the policies were recorded as
240
+ // enabled, so the user is left believing they are covered while no hook was
241
+ // written at all — the silent non-enforcement this file exists to prevent.
242
+ // Skip anything not shaped like a matcher list and leave it untouched.
243
+ const installed = new Set<string>(CLAUDE_INSTALL_EVENT_TYPES);
244
+ for (const eventType of Object.keys(s.hooks)) {
245
+ if (installed.has(eventType)) continue;
246
+ const matchers = s.hooks[eventType];
247
+ if (!Array.isArray(matchers)) continue;
248
+ // Drop a matcher group only when WE emptied it. A group we never touched
249
+ // (no `hooks` array, or one that held nothing of ours) is somebody else's
250
+ // and is written back exactly as found — pruning is for our own entries,
251
+ // not a cleanup pass over the user's file.
252
+ const kept: ClaudeHookMatcher[] = [];
253
+ for (const matcher of matchers as ClaudeHookMatcher[]) {
254
+ if (!matcher || !Array.isArray(matcher.hooks)) {
255
+ if (matcher) kept.push(matcher);
256
+ continue;
257
+ }
258
+ const before = matcher.hooks.length;
259
+ matcher.hooks = matcher.hooks.filter(
260
+ (h) => !isMarkedHook(h as Record<string, unknown>),
261
+ );
262
+ const weEmptiedIt = matcher.hooks.length === 0 && before > 0;
263
+ if (!weEmptiedIt) kept.push(matcher);
264
+ }
265
+ s.hooks[eventType] = kept;
266
+ if (kept.length === 0) delete s.hooks[eventType];
267
+ }
268
+
269
+ for (const eventType of CLAUDE_INSTALL_EVENT_TYPES) {
200
270
  const hookEntry = this.buildHookEntry(binaryPath, eventType, scope) as unknown as ClaudeHookEntry;
201
271
  if (!s.hooks[eventType]) s.hooks[eventType] = [];
202
272
  const matchers: ClaudeHookMatcher[] = s.hooks[eventType];
@@ -1315,10 +1385,19 @@ export const hermes: Integration = {
1315
1385
  scopes: HERMES_HOOK_SCOPES,
1316
1386
  eventTypes: HERMES_HOOK_EVENT_TYPES,
1317
1387
 
1318
- // Hermes config is USER-scope only (`~/.hermes/config.yaml`); there is no
1319
- // project/local file, so scope/cwd are irrelevant here.
1388
+ // Hermes config is USER-scope only (`<HERMES_HOME>/config.yaml`); there is no
1389
+ // project/local file, so scope/cwd are irrelevant here. This is the DEFAULT
1390
+ // profile's config — see getSettingsPaths for the rest.
1320
1391
  getSettingsPath() {
1321
- return resolve(homedir(), ".hermes", "config.yaml");
1392
+ return resolve(hermesRoot(), "config.yaml");
1393
+ },
1394
+
1395
+ // Every profile is a separate Hermes home with its own config.yaml, so a
1396
+ // single-file install would leave non-default profiles running unhooked —
1397
+ // silently, since Hermes never reports a missing hook. Install/uninstall
1398
+ // therefore cover them all.
1399
+ getSettingsPaths() {
1400
+ return listHermesProfiles().map((p) => resolve(p.home, "config.yaml"));
1322
1401
  },
1323
1402
 
1324
1403
  readSettings(settingsPath) {
@@ -1403,21 +1482,13 @@ export const hermes: Integration = {
1403
1482
  return removed;
1404
1483
  },
1405
1484
 
1485
+ // "Installed" means EVERY profile is hooked, not just some. A profile created
1486
+ // after install has no hooks, and reporting green there would hide exactly the
1487
+ // silent-unenforcement gap this integration exists to close; reporting "not
1488
+ // installed" instead prompts a re-install, which writes the missing profiles.
1406
1489
  hooksInstalledInSettings(scope, cwd) {
1407
- const settingsPath = this.getSettingsPath(scope, cwd);
1408
- if (!existsSync(settingsPath)) return false;
1409
- try {
1410
- const doc = readYamlDoc(settingsPath);
1411
- const js = (doc.toJS() ?? {}) as { hooks?: Record<string, HermesHookEntry[]> };
1412
- const hooks = js.hooks;
1413
- if (!hooks || typeof hooks !== "object") return false;
1414
- for (const entries of Object.values(hooks)) {
1415
- if (Array.isArray(entries) && entries.some((h) => isMarkedHook(h))) return true;
1416
- }
1417
- } catch {
1418
- // Corrupt config — treat as not installed.
1419
- }
1420
- return false;
1490
+ const paths = settingsPathsFor(this, scope, cwd);
1491
+ return paths.length > 0 && paths.every((p) => hermesConfigHasHooks(p));
1421
1492
  },
1422
1493
 
1423
1494
  detectInstalled() {
@@ -1425,6 +1496,39 @@ export const hermes: Integration = {
1425
1496
  },
1426
1497
  };
1427
1498
 
1499
+ function hermesConfigHasHooks(settingsPath: string): boolean {
1500
+ if (!existsSync(settingsPath)) return false;
1501
+ try {
1502
+ const doc = readYamlDoc(settingsPath);
1503
+ const js = (doc.toJS() ?? {}) as { hooks?: Record<string, HermesHookEntry[]> };
1504
+ const hooks = js.hooks;
1505
+ if (!hooks || typeof hooks !== "object") return false;
1506
+ for (const entries of Object.values(hooks)) {
1507
+ if (Array.isArray(entries) && entries.some((h) => isMarkedHook(h))) return true;
1508
+ }
1509
+ } catch {
1510
+ // Corrupt config — treat as not installed.
1511
+ }
1512
+ return false;
1513
+ }
1514
+
1515
+ /**
1516
+ * Profiles that exist but have no failproofai hooks — i.e. Hermes instances
1517
+ * currently running unenforced. Empty when Hermes isn't installed at all.
1518
+ *
1519
+ * `listHermesProfiles()` always seeds a `default` entry so install has a path to
1520
+ * write even on a fresh machine, so this filters to homes that actually exist —
1521
+ * otherwise every machine WITHOUT Hermes would report one unhooked profile and
1522
+ * the wizard would replace its accurate "not on PATH" hint with a phantom
1523
+ * warning.
1524
+ */
1525
+ export function unhookedHermesProfiles(): string[] {
1526
+ return listHermesProfiles()
1527
+ .filter((p) => existsSync(p.home))
1528
+ .filter((p) => !hermesConfigHasHooks(resolve(p.home, "config.yaml")))
1529
+ .map((p) => p.name);
1530
+ }
1531
+
1428
1532
  // ── OpenClaw integration ────────────────────────────────────────────────────
1429
1533
  //
1430
1534
  // OpenClaw is a self-hosted assistant gateway. Enforcement is via its in-process
@@ -14,15 +14,16 @@ import {
14
14
  type HookScope,
15
15
  type IntegrationType,
16
16
  } from "./types";
17
- import { claudeCode, getIntegration } from "./integrations";
17
+ import { claudeCode, getIntegration, settingsPathsFor } from "./integrations";
18
18
  import { promptPolicySelection } from "./install-prompt";
19
- import { readMergedHooksConfig, readScopedHooksConfig, writeScopedHooksConfig } from "./hooks-config";
20
- import type { HooksConfig } from "./policy-types";
19
+ import { readMergedHooksConfig, readScopedHooksConfig, writeScopedHooksConfig, syncConventionPolicies, findProjectConfigDir } from "./hooks-config";
20
+ import type { HooksConfig, ConventionPolicyRecord } from "./policy-types";
21
21
  import { BUILTIN_POLICIES } from "./builtin-policies";
22
22
  import { loadCustomHooks, discoverPolicyFiles } from "./custom-hooks-loader";
23
23
  import { trackHookEvent } from "./hook-telemetry";
24
24
  import { getInstanceId, hashToId } from "../../lib/telemetry-id";
25
25
  import { CliError } from "../cli-error";
26
+ import { hookLogWarn } from "./hook-logger";
26
27
 
27
28
  const VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
28
29
 
@@ -261,12 +262,16 @@ async function installHooksImpl(
261
262
  const writtenSettingsPaths: { cli: IntegrationType; path: string }[] = [];
262
263
  for (const cliId of selectedClis) {
263
264
  const integration = getIntegration(cliId);
264
- const settingsPath = integration.getSettingsPath(scope, cwd);
265
+ // Usually one path; Hermes returns one per profile (each is a separate home
266
+ // dir with its own config.yaml, and a missed one runs unhooked in silence).
267
+ const settingsPaths = settingsPathsFor(integration, scope, cwd);
265
268
  try {
266
- const settings = integration.readSettings(settingsPath);
267
- integration.writeHookEntries(settings, binaryPath, scope);
268
- integration.writeSettings(settingsPath, settings);
269
- writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
269
+ for (const settingsPath of settingsPaths) {
270
+ const settings = integration.readSettings(settingsPath);
271
+ integration.writeHookEntries(settings, binaryPath, scope);
272
+ integration.writeSettings(settingsPath, settings);
273
+ writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
274
+ }
270
275
  } catch (err) {
271
276
  const errorType = err instanceof Error && /EACCES|EPERM/.test(err.message)
272
277
  ? "permission_denied"
@@ -452,9 +457,11 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
452
457
  : [];
453
458
 
454
459
  for (const s of scopesToRemove) {
455
- const settingsPath = integration.getSettingsPath(s, cwd);
460
+ // Usually one path; Hermes returns one per profile.
461
+ const settingsPaths = settingsPathsFor(integration, s, cwd);
462
+ const existing = settingsPaths.filter((p) => existsSync(p));
456
463
 
457
- if (!existsSync(settingsPath)) {
464
+ if (existing.length === 0) {
458
465
  if (scope !== "all" && selectedClis.length === 1) {
459
466
  console.log("No settings file found. Nothing to remove.");
460
467
  nothingToReport = true;
@@ -462,18 +469,22 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
462
469
  continue;
463
470
  }
464
471
 
465
- const removed = integration.removeHooksFromFile(settingsPath);
466
- if (removed === 0 && scope !== "all" && selectedClis.length === 1) {
472
+ let removedHere = 0;
473
+ for (const settingsPath of existing) {
474
+ const removed = integration.removeHooksFromFile(settingsPath);
475
+ removedHere += removed;
476
+ if (removed > 0 && scope !== "all") {
477
+ console.log(`Removed ${removed} failproofai hook(s) from ${integration.displayName} settings.`);
478
+ console.log(`Settings: ${settingsPath}`);
479
+ }
480
+ }
481
+
482
+ if (removedHere === 0 && scope !== "all" && selectedClis.length === 1) {
467
483
  console.log("No hooks found in settings. Nothing to remove.");
468
484
  nothingToReport = true;
469
485
  continue;
470
486
  }
471
- totalRemoved += removed;
472
-
473
- if (scope !== "all") {
474
- console.log(`Removed ${removed} failproofai hook(s) from ${integration.displayName} settings.`);
475
- console.log(`Settings: ${settingsPath}`);
476
- }
487
+ totalRemoved += removedHere;
477
488
  }
478
489
  }
479
490
 
@@ -484,7 +495,9 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
484
495
  for (const cliId of selectedClis) {
485
496
  const integration = getIntegration(cliId);
486
497
  for (const s of integration.scopes) {
487
- console.log(` ${integration.displayName} / ${s}: ${integration.getSettingsPath(s, cwd)}`);
498
+ for (const p of settingsPathsFor(integration, s, cwd)) {
499
+ console.log(` ${integration.displayName} / ${s}: ${p}`);
500
+ }
488
501
  }
489
502
  }
490
503
  }
@@ -696,33 +709,86 @@ export async function listHooks(cwd?: string): Promise<void> {
696
709
  }
697
710
 
698
711
  // Convention Policies section (.failproofai/policies/*policies.{js,mjs,ts})
699
- const base = cwd ? resolve(cwd) : process.cwd();
712
+ // Walk up to the project root like enforcement does
713
+ // (custom-hooks-loader -> findProjectConfigDir); resolving at the exact cwd
714
+ // meant running this from a subdirectory listed no project policies while the
715
+ // hook path was loading them.
716
+ const base = findProjectConfigDir(cwd ?? process.cwd());
717
+ const projectDir = resolve(base, ".failproofai", "policies");
718
+ const userDir = resolve(homedir(), ".failproofai", "policies");
719
+ const sameDir = userDir === projectDir;
700
720
  const conventionDirs: { label: string; dir: string }[] = [
701
- { label: "Project", dir: resolve(base, ".failproofai", "policies") },
702
- { label: "User", dir: resolve(homedir(), ".failproofai", "policies") },
721
+ { label: sameDir ? "Project + User" : "Project", dir: projectDir },
722
+ // Running from $HOME makes both paths identical. Listing the directory
723
+ // twice printed every file a second time as "failed to load" — the file was
724
+ // already imported by the first pass, so the module cache short-circuits
725
+ // `customPolicies.add` and `loadCustomHooks` legitimately returns 0 hooks.
726
+ // Nothing was wrong with the policy; the second listing was.
727
+ ...(sameDir ? [] : [{ label: "User", dir: userDir }]),
703
728
  ];
704
729
 
730
+ // Record of what was found, mirrored into policies-config.json below so the
731
+ // config shows installed convention policies and not only enabled builtins.
732
+ const discovered: Record<"project" | "user", ConventionPolicyRecord[]> = {
733
+ project: [],
734
+ user: [],
735
+ };
736
+
705
737
  for (const { label, dir } of conventionDirs) {
706
738
  const files = discoverPolicyFiles(dir);
707
739
  if (files.length === 0) continue;
708
740
 
741
+ // A shared project/user directory is listed once but belongs to both, so
742
+ // its record is written to both scopes' config files.
743
+ const targets: ("project" | "user")[] =
744
+ dir === projectDir && dir === userDir ? ["project", "user"] : dir === projectDir ? ["project"] : ["user"];
745
+ const record = (file: string, hooks: string[]) => {
746
+ for (const t of targets) discovered[t].push({ file, hooks });
747
+ };
748
+
709
749
  console.log(`\n \u2500\u2500 Convention Policies \u2014 ${label} (${dir}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
750
+ // `nameColWidth` is sized to the longest BUILTIN name, but a convention
751
+ // filename can be longer \u2014 `padEnd` is then a no-op and the hook count runs
752
+ // straight into the filename with no space
753
+ // (`enforce-bengaluru-event-links-policies.mjs1 hook(s)`). Widen to fit the
754
+ // files actually being printed, keeping a two-space gutter.
755
+ const colWidth = Math.max(nameColWidth, ...files.map((f) => basename(f).length + 2));
710
756
  for (const file of files) {
711
757
  try {
712
758
  const hooks = await loadCustomHooks(file);
759
+ const filename = basename(file);
760
+ record(filename, hooks.map((h) => h.name));
713
761
  if (hooks.length === 0) {
714
- const filename = basename(file);
715
- console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
762
+ console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(colWidth)}\x1B[31mfailed to load\x1B[0m`);
716
763
  } else {
717
- const filename = basename(file);
718
764
  const hookSummary = hooks.map((h) => h.name).join(", ");
719
- console.log(` \x1B[32m\u2713\x1B[0m ${filename.padEnd(nameColWidth)}${hooks.length} hook(s): ${hookSummary}`);
765
+ console.log(` \x1B[32m\u2713\x1B[0m ${filename.padEnd(colWidth)}${hooks.length} hook(s): ${hookSummary}`);
720
766
  }
721
767
  } catch {
722
768
  const filename = basename(file);
723
- console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31merror\x1B[0m`);
769
+ record(filename, []);
770
+ console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(colWidth)}\x1B[31merror\x1B[0m`);
724
771
  }
725
772
  }
726
773
  console.log();
727
774
  }
775
+
776
+ // Mirror what was just listed into the USER config. Safe here because
777
+ // `failproofai policies` is a one-shot command — never do this on the hook
778
+ // path (see the HooksConfig.conventionPolicies doc comment).
779
+ //
780
+ // User scope only, deliberately. A project's `.failproofai/policies-config.json`
781
+ // is routinely committed (this repo tracks its own), so writing the record
782
+ // there would make a plain `failproofai policies` dirty the working tree and
783
+ // put a spurious diff in front of every contributor — a read command must not
784
+ // do that. `discovered.project` is still collected so the listing above can
785
+ // report it; it simply is not persisted.
786
+ try {
787
+ syncConventionPolicies(discovered.user, "user");
788
+ } catch (err) {
789
+ // Listing must never fail because the mirror could not be written.
790
+ hookLogWarn(
791
+ `could not record convention policies in config: ${err instanceof Error ? err.message : String(err)}`,
792
+ );
793
+ }
728
794
  }
@@ -179,6 +179,21 @@ export async function evaluatePolicies(
179
179
  decision: "deny",
180
180
  };
181
181
  }
182
+ // `beforeSubmitPrompt` does NOT read `permission`. Its only block key
183
+ // is `continue: false` (+ an optional `user_message` shown to the
184
+ // user); an object carrying unknown keys validates and is dropped, so
185
+ // the flat deny below was inert and every prompt went through. Verified
186
+ // against cursor-agent 2026.07.16-899851b, 1931.index.js char 887883.
187
+ if (eventType === "UserPromptSubmit") {
188
+ return {
189
+ exitCode: 0,
190
+ stdout: JSON.stringify({ continue: false, user_message: blockedMessage }),
191
+ stderr: "",
192
+ policyName: policy.name,
193
+ reason,
194
+ decision: "deny",
195
+ };
196
+ }
182
197
  const response = {
183
198
  permission: "deny",
184
199
  user_message: blockedMessage,
@@ -229,13 +244,15 @@ export async function evaluatePolicies(
229
244
  }
230
245
 
231
246
  // Hermes: the block contract is `{"decision":"block","reason"}` on stdout;
232
- // Hermes IGNORES exit codes, so the JSON is the only channel. This one
233
- // return covers every Hermes event we install (PreToolUse / PostToolUse /
234
- // SubagentStop) — there is no Stop/turn-end event to special-case (Hermes
235
- // has no turn boundary; the 5 require-*-before-stop builtins never fire for
236
- // it — see CLAUDE.md / the audit plan). A block on PreToolUse stops the
237
- // tool before it runs, regardless of the originating platform
238
- // (slack/telegram/cli/cron) or subagent.
247
+ // Hermes IGNORES exit codes, so the JSON is the only channel. A block on
248
+ // PreToolUse stops the tool before it runs, regardless of the originating
249
+ // platform (slack/telegram/cli/cron) or subagent.
250
+ //
251
+ // Only `pre_tool_call` and `pre_verify` are gated in upstream's
252
+ // `_parse_response` (agent/shell_hooks.py:567-621); the shape is still
253
+ // emitted for the other installed events, where it is read by nothing.
254
+ // We do not install `pre_verify` (see the note in types.ts), so Hermes
255
+ // has no canonical Stop event and no Stop branch is needed here.
239
256
  if (session?.cli === "hermes") {
240
257
  return {
241
258
  exitCode: 0,
@@ -422,6 +439,41 @@ export async function evaluatePolicies(
422
439
  };
423
440
  }
424
441
 
442
+ // Copilot reads two shapes we were not sending, so a deny on either of
443
+ // these events was emitted, logged, counted as enforcement — and ignored.
444
+ // Both verified against the shipped @github/copilot-linux-x64 bundle.
445
+ // Note exit 2 is NEVER a deny channel on copilot for any event; it is
446
+ // logged as `Hook command exited with code 2 (warning)`.
447
+ if (session?.cli === "copilot") {
448
+ // `userPromptSubmit` gates the turn, but only on {decision:"block"} at
449
+ // exit 0 (consumer app.js@2823018). We were emitting exit 2 + stderr,
450
+ // which copilot warns about and then submits the prompt anyway.
451
+ if (eventType === "UserPromptSubmit") {
452
+ return {
453
+ exitCode: 0,
454
+ stdout: JSON.stringify({ decision: "block", reason: blockedMessage }),
455
+ stderr: "",
456
+ policyName: policy.name,
457
+ reason,
458
+ decision: "deny",
459
+ };
460
+ }
461
+ // Copilot consumes a FLAT {behavior, message} here (normalizer
462
+ // CMn@179042 -> mapper h4t@2686538). The Codex-shaped nested
463
+ // hookSpecificOutput.decision below normalizes to `{}` on copilot, so
464
+ // the permission prompt proceeded as if no policy existed.
465
+ if (eventType === "PermissionRequest") {
466
+ return {
467
+ exitCode: 0,
468
+ stdout: JSON.stringify({ behavior: "deny", message: blockedMessage }),
469
+ stderr: "",
470
+ policyName: policy.name,
471
+ reason,
472
+ decision: "deny",
473
+ };
474
+ }
475
+ }
476
+
425
477
  if (eventType === "PermissionRequest") {
426
478
  // Codex-only: hookSpecificOutput.decision.behavior = "allow" | "deny"
427
479
  // (per https://developers.openai.com/codex/hooks#permissionrequest).
@@ -93,4 +93,30 @@ export interface HooksConfig {
93
93
  * Only ever written explicitly as `false`.
94
94
  */
95
95
  customPoliciesEnabled?: boolean;
96
+ /**
97
+ * Record of the convention policies (`.failproofai/policies/*policies.mjs`)
98
+ * discovered at this scope the last time a failproofai command ran.
99
+ *
100
+ * DESCRIPTIVE, NEVER AUTHORITATIVE. Enforcement always discovers from the
101
+ * filesystem (`loadAllCustomHooks`) and never reads this key — a policy file
102
+ * dropped in enforces on the very next tool call whether or not this list has
103
+ * caught up. Making the loader trust it would mean a freshly-copied policy
104
+ * silently doing nothing until some command refreshed the config, which is
105
+ * the exact silent-non-enforcement failure this project exists to remove.
106
+ *
107
+ * Refreshed wholesale (not merged) by `failproofai policies` and the setup
108
+ * wizard, so a deleted file disappears from the record on the next run. It is
109
+ * deliberately NOT written from the hook path: that runs on every tool call,
110
+ * and a read-modify-write from concurrent short-lived hook processes with no
111
+ * locking would corrupt the file that governs enforcement.
112
+ */
113
+ conventionPolicies?: ConventionPolicyRecord[];
114
+ }
115
+
116
+ /** One convention policy file and the hooks it registered, as last observed. */
117
+ export interface ConventionPolicyRecord {
118
+ /** Basename, e.g. `hermes-guardrails.policies.mjs`. */
119
+ file: string;
120
+ /** Hook names the file registered, or `[]` if it failed to load. */
121
+ hooks: string[];
96
122
  }
@@ -105,8 +105,34 @@ export const HERMES_TOOL_INPUT_MAP: Record<string, Record<string, string>> = {
105
105
  // (`~/.hermes/config.yaml`; Hermes has no project scope). `pre_tool_call` is the
106
106
  // core deny point — it fires for tool calls from every source
107
107
  // (slack/telegram/cli/cron) and internal subagents, so a single install
108
- // intercepts all platforms. Hermes has NO turn-end `Stop` event, so the
109
- // `require-*-before-stop` builtins never fire for it (see the audit plan).
108
+ // intercepts all platforms.
109
+ //
110
+ // `pre_verify` IS a turn-end gate — the earlier claim here that Hermes has none
111
+ // was wrong. We deliberately do NOT install it (product decision, 2026-07-29);
112
+ // this note records what it would buy so the choice can be revisited without
113
+ // re-deriving it.
114
+ //
115
+ // Upstream fires it once per turn when the agent has edited code and is about to
116
+ // finish (`agent/conversation_loop.py:6754`), and its parser accepts our Claude
117
+ // Stop shape verbatim — `{decision:"block", reason}` means "block the stop",
118
+ // i.e. keep going (`agent/shell_hooks.py:606-615`). The reason is injected as a
119
+ // synthetic user message and the loop re-enters
120
+ // (`conversation_loop.py:6774-6800`). Installing it is one entry in
121
+ // config.yaml — it is a shell hook like the other five, no Python involved.
122
+ //
123
+ // It would make the 5 `require-*-before-stop` builtins fire on Hermes, subject
124
+ // to three upstream conditions:
125
+ // 1. It fires ONLY on turns that landed a file mutation, and that means
126
+ // exactly `write_file` or `patch` (`agent/tool_result_classification.py:9`)
127
+ // — a turn that did its work through `terminal` (sed -i, rm, >) does not
128
+ // qualify, so a chat-only gateway may never see it.
129
+ // 2. Capped at 3 nudges per turn (`DEFAULT_MAX_VERIFY_NUDGES`,
130
+ // `agent/verify_hooks.py:21`), operator-overridable; resets each turn.
131
+ // 3. It landed upstream ~2026-06-30. Older Hermes fails the key against
132
+ // VALID_HOOKS and warn-and-skips it SILENTLY (`agent/shell_hooks.py:325`).
133
+ //
134
+ // Until it is installed, `HERMES_EVENT_MAP` emits no `Stop` and those 5
135
+ // builtins remain inapplicable on Hermes.
110
136
  export const HERMES_HOOK_SCOPES = ["user"] as const;
111
137
  export type HermesHookScope = (typeof HERMES_HOOK_SCOPES)[number];
112
138
 
@@ -1002,8 +1028,30 @@ export const HOOK_EVENT_TYPES = [
1002
1028
  "Setup",
1003
1029
  ] as const;
1004
1030
 
1031
+
1032
+
1005
1033
  export type HookEventType = (typeof HOOK_EVENT_TYPES)[number];
1006
1034
 
1035
+ /**
1036
+ * Events failproofai actually INSTALLS a hook for on Claude Code.
1037
+ *
1038
+ * Everything in `HOOK_EVENT_TYPES` except `WorktreeCreate`, which is not a
1039
+ * permission gate at all: Claude uses it as a worktree-PATH PROVIDER, taking
1040
+ * the stdout of the first hook that succeeds as the directory to create and
1041
+ * failing with "WorktreeCreate hook failed" when no hook supplies one. Our
1042
+ * allow path writes nothing to stdout — correctly, by the contract every other
1043
+ * event uses — so merely being registered there broke `claude --worktree` and
1044
+ * `/worktree` for every user, whatever any policy decided.
1045
+ *
1046
+ * It stays in `HOOK_EVENT_TYPES` because that list is the canonical set a
1047
+ * policy may subscribe to; this one governs what we write into settings.json.
1048
+ * No builtin matches it (all 39 match only PreToolUse / PostToolUse /
1049
+ * PermissionRequest / Stop), so nothing is lost by not registering.
1050
+ */
1051
+ export const CLAUDE_INSTALL_EVENT_TYPES = HOOK_EVENT_TYPES.filter(
1052
+ (e) => e !== "WorktreeCreate",
1053
+ ) as readonly HookEventType[];
1054
+
1007
1055
  export const FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__" as const;
1008
1056
 
1009
1057
  export interface ClaudeHookEntry {