failproofai 1.0.4-beta.1 → 1.0.4-beta.2

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 (340) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  3. package/.next/standalone/.next/prerender-manifest.json +5 -5
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  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.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/invite/route_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
  26. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/run/route_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/status/route_client-reference-manifest.js +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/login-request/route_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-verify/route_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  42. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +37 -4
  43. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  44. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/index.html +1 -1
  47. package/.next/standalone/.next/server/app/index.rsc +15 -15
  48. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  49. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  50. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  51. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  52. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  53. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  57. package/.next/standalone/.next/server/app/policies/page.js +1 -1
  58. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
  63. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  66. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  67. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  68. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  73. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  74. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  77. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  78. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/chunks/{[externals]__1zndo1i._.js → [externals]__1lh7m5d._.js} +1 -1
  81. package/.next/standalone/.next/server/chunks/{[externals]__0_gzk8q._.js → [externals]__1rqkg_y._.js} +1 -1
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +3 -0
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
  88. package/.next/standalone/.next/server/chunks/_08w6xzm._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +14 -21
  93. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  95. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  96. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00-s7h8._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
  104. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0wvusu0._.js → [root-of-the-server]__0n_lxhg._.js} +2 -2
  106. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rgu2r3._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__190won8._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1sc84bb._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zl_r0t._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/_0-oij9d._.js +23 -0
  117. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/_0oq1dh6._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/_0wso5d3._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +48 -168
  123. package/.next/standalone/.next/server/chunks/ssr/{_08f0jd2._.js → _1if9ixi._.js} +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  126. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -69
  127. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
  133. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  135. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  136. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  137. package/.next/standalone/.next/server/pages/404.html +1 -1
  138. package/.next/standalone/.next/server/pages/500.html +1 -1
  139. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  140. package/.next/standalone/.next/server/server-reference-manifest.json +54 -21
  141. package/.next/standalone/.next/static/chunks/{1_bxotmf9qngs.js → 010bv1w6j171t.js} +1 -1
  142. package/.next/standalone/.next/static/chunks/04r6ch8uf_n8m.js +1 -0
  143. package/.next/standalone/.next/static/chunks/{29-iqnp9v_axt.css → 0o-hh5_turzlz.css} +1 -1
  144. package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +2 -0
  145. package/.next/standalone/.next/static/chunks/{00gv0v91mycm-.js → 0wz8yftk18ts2.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/0zebh1n9jkfbt.js +1 -0
  147. package/.next/standalone/.next/static/chunks/{0qn9khv0oo9ex.js → 1pb1oztsbwcss.js} +1 -1
  148. package/.next/standalone/.next/static/chunks/2aquitk72k2op.js +1 -0
  149. package/.next/standalone/.next/static/chunks/2bi_1y0a_smt7.js +6 -0
  150. package/.next/standalone/.next/static/chunks/{1po3lirgk2riw.js → 2ej3b8gk5ittu.js} +1 -1
  151. package/.next/standalone/.next/static/chunks/{01hvwwrhvbzyh.js → 2zafgs90r_leq.js} +1 -1
  152. package/.next/standalone/.next/static/chunks/{1_2jsatfuthrw.js → 32spub4wqjem-.js} +1 -1
  153. package/.next/standalone/.next/static/chunks/{3_--3ibia6ba1.js → 3l36d73kj18zw.js} +1 -1
  154. package/.next/standalone/.next/static/chunks/{2bvwsyu6x__ia.js → 3m4upvybtrexd.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{2k9f4tyv04809.css → 3uyhmm01g64k4.css} +1 -0
  156. package/.next/standalone/.next/static/chunks/{2bnf_4pgyk1rq.js → 40lpt2o2b283e.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{turbopack-18qf4jodtft6b.js → turbopack-3s3c-u_u_l0u9.js} +1 -1
  158. package/.next/standalone/.opencode/plugins/failproofai.mjs +51 -27
  159. package/.next/standalone/app/actions/get-leaks.ts +100 -0
  160. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +112 -33
  161. package/.next/standalone/app/audit/_components/audit-poster.tsx +29 -10
  162. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +6 -1
  163. package/.next/standalone/app/audit/_components/empty-state.tsx +4 -1
  164. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +19 -5
  165. package/.next/standalone/app/audit/_components/leak-section.tsx +179 -0
  166. package/.next/standalone/app/audit/_components/share-templates.ts +110 -44
  167. package/.next/standalone/app/audit/audit-styles.css +114 -1
  168. package/.next/standalone/app/project/[name]/page.tsx +4 -24
  169. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +26 -3
  170. package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +31 -8
  171. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +1 -0
  172. package/.next/standalone/fp-cloud-cli/pyproject.toml +9 -2
  173. package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +54 -0
  174. package/.next/standalone/fp-cloud-cli/uv.lock +9 -12
  175. package/.next/standalone/lib/auth/api-server-client.ts +28 -0
  176. package/.next/standalone/lib/claude-sessions.ts +101 -27
  177. package/.next/standalone/lib/cli-registry.ts +1 -33
  178. package/.next/standalone/lib/download-session.ts +0 -37
  179. package/.next/standalone/lib/projects.ts +3 -27
  180. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  181. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  182. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
  183. package/.next/standalone/node_modules/next/dist/experimental/testmode/fetch.js +10 -2
  184. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  185. package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeScriptCli.js +2 -2
  186. package/.next/standalone/node_modules/next/dist/lib/verify-typescript-setup.js +3 -1
  187. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  188. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  189. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  190. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +2 -2
  191. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  192. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  193. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  194. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  195. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  196. package/.next/standalone/node_modules/next/package.json +11 -11
  197. package/.next/standalone/package.json +15 -14
  198. package/.next/standalone/sdk/python/CHANGELOG.md +155 -0
  199. package/.next/standalone/sdk/python/README.md +8 -0
  200. package/.next/standalone/sdk/python/examples/evaluator_worker.py +121 -0
  201. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  202. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +101 -0
  203. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__main__.py +49 -0
  204. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/_sandbox_runner.py +63 -0
  205. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +404 -0
  206. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/client.py +299 -0
  207. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/protocol.py +754 -0
  208. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +936 -0
  209. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/source.py +684 -0
  210. package/.next/standalone/sdk/python/tests/fixtures/evaluator_v2/README.md +28 -0
  211. package/.next/standalone/sdk/python/tests/fixtures/evaluator_v2/contract.json +252 -0
  212. package/.next/standalone/sdk/python/tests/test_evaluator_authoring.py +124 -0
  213. package/.next/standalone/sdk/python/tests/test_evaluator_client.py +253 -0
  214. package/.next/standalone/sdk/python/tests/test_evaluator_example.py +35 -0
  215. package/.next/standalone/sdk/python/tests/test_evaluator_http_e2e.py +636 -0
  216. package/.next/standalone/sdk/python/tests/test_evaluator_main.py +47 -0
  217. package/.next/standalone/sdk/python/tests/test_evaluator_protocol.py +246 -0
  218. package/.next/standalone/sdk/python/tests/test_evaluator_review_fixes.py +164 -0
  219. package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +1103 -0
  220. package/.next/standalone/sdk/python/tests/test_evaluator_source.py +430 -0
  221. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +17 -0
  222. package/.next/standalone/sdk/python/uv.lock +18 -18
  223. package/.next/standalone/server.js +1 -1
  224. package/README.md +19 -95
  225. package/bin/failproofai.mjs +7 -24
  226. package/dist/cli.mjs +19910 -25855
  227. package/dist/worker.mjs +4818 -6344
  228. package/lib/auth/api-server-client.ts +28 -0
  229. package/lib/claude-sessions.ts +101 -27
  230. package/lib/cli-registry.ts +1 -33
  231. package/lib/download-session.ts +0 -37
  232. package/lib/projects.ts +3 -27
  233. package/package.json +15 -14
  234. package/pi-extension/index.ts +42 -4
  235. package/scripts/changelog-open.py +115 -0
  236. package/src/audit/cli-adapters/index.ts +0 -24
  237. package/src/audit/cli.ts +134 -0
  238. package/src/audit/desktop-notify.ts +420 -0
  239. package/src/audit/harm-report.ts +101 -0
  240. package/src/audit/index.ts +141 -13
  241. package/src/audit/leak-fingerprint.ts +200 -0
  242. package/src/audit/leak-notice.ts +161 -0
  243. package/src/audit/leak-record.ts +232 -0
  244. package/src/audit/leak-scan.ts +292 -0
  245. package/src/audit/leak-store.ts +217 -0
  246. package/src/audit/macos-notifier.ts +310 -0
  247. package/src/audit/redact-example.ts +266 -14
  248. package/src/audit/report-harm.ts +12 -1
  249. package/src/audit/report.ts +6 -3
  250. package/src/audit/schedule-cli.ts +37 -0
  251. package/src/audit/scoring.ts +49 -0
  252. package/src/audit/types.ts +41 -0
  253. package/src/hooks/builtin-policies.ts +71 -8
  254. package/src/hooks/configure-wizard.ts +20 -0
  255. package/src/hooks/enforcement-capability.ts +0 -97
  256. package/src/hooks/fp-config.ts +71 -11
  257. package/src/hooks/fp-home.ts +27 -0
  258. package/src/hooks/handler.ts +64 -62
  259. package/src/hooks/harness-cli.ts +1 -3
  260. package/src/hooks/integrations.ts +49 -769
  261. package/src/hooks/normalize-cli-payload.ts +0 -126
  262. package/src/hooks/notice.ts +155 -0
  263. package/src/hooks/policy-evaluator.ts +6 -297
  264. package/src/hooks/resolve-transcript-path.ts +0 -10
  265. package/src/hooks/tool-name-canonicalize.ts +0 -64
  266. package/src/hooks/types.ts +2 -533
  267. package/src/hooks/uninstall-cli.ts +15 -0
  268. package/.next/standalone/.grok/hooks/failproofai.json +0 -172
  269. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07hor7h._.js +0 -5
  270. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0elsuli._.js +0 -3
  271. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0gnj_qa._.js +0 -3
  272. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljuzm_._.js +0 -3
  273. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w23bpg._.js +0 -3
  274. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0x5c7sh._.js +0 -3
  275. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yik9_w._.js +0 -3
  276. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1jg6s9m._.js +0 -3
  277. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1s6qci9._.js +0 -3
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xfm05z._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/_0tdy_6l._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/lib_grok-projects_ts_1i2epl_._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/lib_qwen-projects_ts_0wu_a8e._.js +0 -3
  282. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_4gcz8._.js +0 -3
  283. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0d--wmy._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qqrlf_._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s11fx1._.js +0 -3
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gamwq._.js +0 -3
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bbk-si._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1c3ybxr._.js +0 -3
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1izi7zn._.js +0 -3
  290. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__21bj7uw._.js +0 -3
  291. package/.next/standalone/.next/server/chunks/ssr/_00z5uuq._.js +0 -3
  292. package/.next/standalone/.next/server/chunks/ssr/_1byck5v._.js +0 -23
  293. package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +0 -3
  294. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +0 -3
  295. package/.next/standalone/.next/server/chunks/ssr/lib_cline-projects_ts_13_hbdd._.js +0 -3
  296. package/.next/standalone/.next/server/chunks/ssr/lib_grok-projects_ts_0rc-d0o._.js +0 -3
  297. package/.next/standalone/.next/server/chunks/ssr/lib_ori-projects_ts_04tofqo._.js +0 -3
  298. package/.next/standalone/.next/server/chunks/ssr/lib_qwen-projects_ts_0w3i9o7._.js +0 -3
  299. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +0 -3
  300. package/.next/standalone/.next/static/chunks/0hzmxwb2pvk5p.js +0 -6
  301. package/.next/standalone/.next/static/chunks/0j884dwnwtyrt.js +0 -1
  302. package/.next/standalone/.next/static/chunks/0mr9dg3bnloa1.js +0 -1
  303. package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +0 -1
  304. package/.next/standalone/.next/static/chunks/3gf1jg-5m-u1t.css +0 -2
  305. package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +0 -1
  306. package/.next/standalone/.next/static/chunks/3ws7eljkq25wg.js +0 -69
  307. package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +0 -1
  308. package/.next/standalone/.qwen/settings.json +0 -251
  309. package/.next/standalone/assets/logos/cline.png +0 -0
  310. package/.next/standalone/assets/logos/grok-dark.svg +0 -1
  311. package/.next/standalone/assets/logos/grok-light.svg +0 -1
  312. package/.next/standalone/assets/logos/ori-dark.svg +0 -1
  313. package/.next/standalone/assets/logos/ori-light.svg +0 -1
  314. package/.next/standalone/assets/logos/qwen.svg +0 -1
  315. package/.next/standalone/lib/cline-projects.ts +0 -155
  316. package/.next/standalone/lib/cline-sessions.ts +0 -268
  317. package/.next/standalone/lib/grok-projects.ts +0 -123
  318. package/.next/standalone/lib/grok-sessions.ts +0 -355
  319. package/.next/standalone/lib/ori-projects.ts +0 -141
  320. package/.next/standalone/lib/ori-sessions.ts +0 -221
  321. package/.next/standalone/lib/qwen-projects.ts +0 -106
  322. package/.next/standalone/lib/qwen-sessions.ts +0 -313
  323. package/lib/cline-projects.ts +0 -155
  324. package/lib/cline-sessions.ts +0 -268
  325. package/lib/grok-projects.ts +0 -123
  326. package/lib/grok-sessions.ts +0 -355
  327. package/lib/ori-projects.ts +0 -141
  328. package/lib/ori-sessions.ts +0 -221
  329. package/lib/qwen-projects.ts +0 -106
  330. package/lib/qwen-sessions.ts +0 -313
  331. package/src/audit/cli-adapters/cline.ts +0 -50
  332. package/src/audit/cli-adapters/grok.ts +0 -70
  333. package/src/audit/cli-adapters/ori.ts +0 -55
  334. package/src/audit/cli-adapters/qwen.ts +0 -71
  335. package/src/hooks/batch-expand.ts +0 -380
  336. package/src/hooks/batch-fanout.ts +0 -142
  337. package/src/hooks/risk-patterns.ts +0 -26
  338. /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → aKNv4Kmv98Xpwns0Xa-C-}/_buildManifest.js +0 -0
  339. /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → aKNv4Kmv98Xpwns0Xa-C-}/_clientMiddlewareManifest.js +0 -0
  340. /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → aKNv4Kmv98Xpwns0Xa-C-}/_ssgManifest.js +0 -0
@@ -10,56 +10,6 @@
10
10
  */
11
11
  import type { IntegrationType } from "./types";
12
12
 
13
- /**
14
- * True when `parsed` carries grok's camelCase envelope.
15
- *
16
- * This exists because grok EXECUTES OTHER CLIS' HOOK CONFIGS. Its discovery
17
- * scans `~/.claude/settings.json`, `~/.claude/settings.local.json`,
18
- * `<cwd>/.claude/settings.json` and the `.cursor` equivalents by default
19
- * (`[compat.claude] hooks = true`) — and `<cwd>/.claude/settings.json` is
20
- * exactly the file `policies --install --cli claude --scope project` writes. So
21
- * on any machine with both tools, grok runs OUR hooks and hands them `--cli
22
- * claude` on the command line while piping ITS OWN camelCase payload.
23
- *
24
- * Verified live against grok 1.0.3: the hook fired, `tool_name` and
25
- * `tool_input` were both undefined, and every builtin that reads either —
26
- * block-sudo, block-env-files, block-secrets-write, block-force-push — allowed.
27
- * Installed, running, costing latency, enforcing nothing; strictly worse than
28
- * no coverage, because the install reports success. Detecting the envelope lets
29
- * handler.ts re-route the event onto grok's path (tool maps AND response
30
- * shapes — grok ignores Claude's `hookSpecificOutput.permissionDecision`, also
31
- * verified by A/B).
32
- *
33
- * The test is on the PAYLOAD, never on an env var: `GROK_HOOK_EVENT` is set by
34
- * grok's runner but is still just an env var, and mistaking a real Claude event
35
- * for a grok one would break Claude's own enforcement. `workspaceRoot` +
36
- * `hookEventName` with no `hook_event_name` is a shape Claude never sends.
37
- */
38
- export function isGrokEnvelope(parsed: Record<string, unknown>): boolean {
39
- return (
40
- typeof parsed.hookEventName === "string" &&
41
- typeof parsed.workspaceRoot === "string" &&
42
- parsed.hook_event_name === undefined
43
- );
44
- }
45
-
46
- /**
47
- * The CLI whose contract actually governs this event.
48
- *
49
- * Identical to the declared `--cli` for every integration except the grok
50
- * cross-execution case above, where the flag says `claude` and the wire says
51
- * grok. Returning "grok" there routes the rest of the pipeline — tool-name and
52
- * tool-input canonicalization, and the response shape policy-evaluator emits —
53
- * onto the contract that will actually be honored.
54
- */
55
- export function resolveEffectiveCli(
56
- cli: IntegrationType,
57
- parsed: Record<string, unknown>,
58
- ): IntegrationType {
59
- if (cli === "claude" && isGrokEnvelope(parsed)) return "grok";
60
- return cli;
61
- }
62
-
63
13
  export function normalizeCliPayload(cli: IntegrationType, parsed: Record<string, unknown>): void {
64
14
  // Antigravity (agy) pipes a camelCase protojson payload; normalize the fields
65
15
  // the handler downstream reads to canonical snake_case BEFORE any
@@ -97,33 +47,6 @@ export function normalizeCliPayload(cli: IntegrationType, parsed: Record<string,
97
47
  }
98
48
  }
99
49
 
100
- // grok pipes a camelCase envelope (verified live against grok 1.0.3 with a
101
- // recorder hook on all 14 events). Normalize the fields the handler reads to
102
- // snake_case BEFORE any canonicalization runs. Note `workspaceRoot` → `cwd`:
103
- // grok sends BOTH, they were identical in every capture, and `cwd` is what
104
- // resolveCwd + block-read-outside-cwd need. `toolResult` is grok's PostToolUse
105
- // output key — it is NOT Claude's `tool_response`, so a sanitize policy
106
- // reading the documented Claude key would see nothing without this line.
107
- // `hookEventName` is deliberately NOT mapped: its value is snake_case
108
- // ("pre_tool_use") whereas the canonical set is PascalCase, and the `--hook`
109
- // arg already carries the PascalCase name.
110
- if (cli === "grok") {
111
- if (typeof parsed.toolName === "string") parsed.tool_name = parsed.toolName;
112
- if (parsed.toolInput !== undefined) parsed.tool_input = parsed.toolInput;
113
- if (parsed.toolResult !== undefined && parsed.tool_response === undefined) {
114
- parsed.tool_response = parsed.toolResult;
115
- }
116
- if (typeof parsed.sessionId === "string") parsed.session_id = parsed.sessionId;
117
- if (typeof parsed.transcriptPath === "string") parsed.transcript_path = parsed.transcriptPath;
118
- if (typeof parsed.workspaceRoot === "string" && parsed.cwd === undefined) {
119
- parsed.cwd = parsed.workspaceRoot;
120
- }
121
- if (typeof parsed.permissionMode === "string") parsed.permission_mode = parsed.permissionMode;
122
- if (typeof parsed.stopHookActive === "boolean") {
123
- parsed.stop_hook_active = parsed.stopHookActive;
124
- }
125
- }
126
-
127
50
  // Goose pipes `event` / `working_dir` instead of Claude's `hook_event_name` /
128
51
  // `cwd` (its `tool_name` / `tool_input` are already canonical field names).
129
52
  // Normalize both so resolveCwd keeps its cwd (block-read-outside-cwd) and the
@@ -135,53 +58,4 @@ export function normalizeCliPayload(cli: IntegrationType, parsed: Record<string,
135
58
  parsed.hook_event_name = parsed.event;
136
59
  }
137
60
  }
138
-
139
- // Cline pipes its own payload verbatim — its hooks are event-NAMED scripts,
140
- // not a generated shim that could pre-shape stdin — so without this every
141
- // field the handler reads (tool_name, tool_input, session_id, cwd) is
142
- // undefined and resolveCwd / block-read-outside-cwd see nothing. Verified live
143
- // against cline v3.0.60 with a recorder hook on PreToolUse.
144
- //
145
- // `tool_call.input` is the source, NOT `preToolUse.parameters`: the latter
146
- // JSON-STRINGIFIES every array value (`commands` arrives as the literal string
147
- // `["echo hi"]`), so reading it would hand the batch expander a string and
148
- // silently drop the entire fan-out. `preToolUse` is used only as a fallback
149
- // for a payload carrying no `tool_call` at all.
150
- //
151
- // `hookName` is deliberately NOT mapped onto hook_event_name: its value is
152
- // cline's INTERNAL name ("tool_call"), not the filename event, and the --hook
153
- // argument already carries the canonical one. Same reasoning as grok's
154
- // hookEventName.
155
- if (cli === "cline") {
156
- const tc = parsed.tool_call;
157
- if (tc && typeof tc === "object" && !Array.isArray(tc)) {
158
- const t = tc as Record<string, unknown>;
159
- if (typeof t.name === "string") parsed.tool_name = t.name;
160
- if (t.input !== undefined) parsed.tool_input = t.input;
161
- }
162
- const pre = parsed.preToolUse;
163
- if (pre && typeof pre === "object" && !Array.isArray(pre)) {
164
- const pp = pre as Record<string, unknown>;
165
- if (parsed.tool_name === undefined && typeof pp.toolName === "string") {
166
- parsed.tool_name = pp.toolName;
167
- }
168
- if (parsed.tool_input === undefined && pp.parameters !== undefined) {
169
- parsed.tool_input = pp.parameters;
170
- }
171
- }
172
- // taskId is the conversation ("conv_…"). sessionContext.rootSessionId and
173
- // agent_id / parent_agent_id stay raw for custom policies correlating a
174
- // subagent back to its root.
175
- if (typeof parsed.taskId === "string") parsed.session_id = parsed.taskId;
176
- const roots = parsed.workspaceRoots;
177
- if (Array.isArray(roots) && typeof roots[0] === "string" && roots[0].length > 0) {
178
- parsed.cwd = roots[0];
179
- } else {
180
- const wi = parsed.workspaceInfo;
181
- if (wi && typeof wi === "object" && !Array.isArray(wi)) {
182
- const rp = (wi as Record<string, unknown>).rootPath;
183
- if (typeof rp === "string" && rp.length > 0) parsed.cwd = rp;
184
- }
185
- }
186
- }
187
61
  }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Putting a short message in front of a HUMAN, inside the agent they are using.
3
+ *
4
+ * A notice is not a verdict. `allow` / `deny` / `instruct` all answer "what
5
+ * happens to this tool call"; a notice answers nothing — it is one line of text
6
+ * for the person, delivered alongside whatever the verdict turned out to be.
7
+ * That is why it is a separate field rather than a fourth decision: shaping it
8
+ * into stdout beside a verdict ad hoc is how the copilot nested-vs-flat
9
+ * `additionalContext` bug happened.
10
+ *
11
+ * ## Why the channel differs per CLI, and why it is not `additionalContext`
12
+ *
13
+ * Measured across all 12 CLIs by live probe. The obvious channel —
14
+ * `hookSpecificOutput.additionalContext` — reaches the MODEL, not the user, and
15
+ * a model is a paraphraser: over eight real Claude turns it mentioned an
16
+ * informational notice 8/8, but an earlier notice that asked the agent to *act*
17
+ * was disclaimed as a suspected prompt injection 6/6 ("instructions arriving
18
+ * that way aren't something I'll act on unasked"). A security notice routed
19
+ * through something that may decline to repeat it is not a delivered notice.
20
+ *
21
+ * So each CLI gets the channel that was proven to paint text on the user's
22
+ * terminal, and the ones with no such channel get nothing rather than a
23
+ * pretend delivery:
24
+ *
25
+ * claude `{"systemMessage":…}` renders `Stop says: …`; model-invisible
26
+ * (a neutral probe answered `NONE`)
27
+ * codex `{"systemMessage":…}` renders `• Stop (completed) says: …`
28
+ * copilot exit 0 + stderr renders as a `!` session warning
29
+ * factory plain stdout renders under a `Hooks Stop` block
30
+ *
31
+ * opencode, pi and openclaw have real channels too, but they live inside the
32
+ * plugin shims we ship rather than in a hook's stdout, so they are wired there.
33
+ * cursor, devin, goose, antigravity and hermes have no channel that reaches a
34
+ * human without an unsupported escape hatch — they get nothing here, and their
35
+ * users get the finding from `failproofai audit` and the emailed digest.
36
+ */
37
+ import type { IntegrationType } from "./types";
38
+
39
+ /** How a notice is delivered on one CLI. */
40
+ type NoticeChannel = "systemMessage" | "stderr" | "stdout" | "none";
41
+
42
+ /**
43
+ * Per-CLI channel, from live probes rather than documentation.
44
+ *
45
+ * A CLI absent from this map delivers nothing — deliberately. Guessing a
46
+ * channel produces the worst outcome available: output that looks delivered
47
+ * from our side and reaches nobody, so we would report a leak as "notified"
48
+ * that the user never saw.
49
+ */
50
+ const NOTICE_CHANNEL: Partial<Record<IntegrationType, NoticeChannel>> = {
51
+ claude: "systemMessage",
52
+ codex: "systemMessage",
53
+ copilot: "stderr",
54
+ factory: "stdout",
55
+ };
56
+
57
+ /**
58
+ * Claude truncates `systemMessage` at 4000 characters on the cloud-forwarding
59
+ * path, and a notice that gets cut mid-sentence reads as a broken tool. Ours is
60
+ * two lines by design; this is a backstop, not a budget.
61
+ */
62
+ const MAX_NOTICE_CHARS = 900;
63
+
64
+ export interface ShapedNotice {
65
+ /** Merged into the outcome's stdout, or empty. */
66
+ stdout: string;
67
+ /** Merged into the outcome's stderr, or empty. */
68
+ stderr: string;
69
+ }
70
+
71
+ /**
72
+ * Render a notice for one CLI, merging it into any verdict JSON already bound
73
+ * for stdout.
74
+ *
75
+ * `existingStdout` matters: a Stop-gate deny already emits a JSON object on
76
+ * several CLIs, and emitting a second object would produce two JSON documents
77
+ * on one stream — which every host parses as a syntax error and drops, taking
78
+ * the deny with it. So the notice is merged INTO that object when there is one.
79
+ */
80
+ export function shapeNotice(
81
+ cli: IntegrationType | undefined,
82
+ notice: string,
83
+ existingStdout = "",
84
+ ): ShapedNotice {
85
+ const empty: ShapedNotice = { stdout: existingStdout, stderr: "" };
86
+ if (!notice.trim() || !cli) return empty;
87
+
88
+ const channel = NOTICE_CHANNEL[cli] ?? "none";
89
+ const text = notice.length > MAX_NOTICE_CHARS
90
+ ? notice.slice(0, MAX_NOTICE_CHARS - 1) + "…"
91
+ : notice;
92
+
93
+ switch (channel) {
94
+ case "systemMessage": {
95
+ // Merge into the verdict object when one exists, so the stream stays a
96
+ // single JSON document.
97
+ if (existingStdout.trim().startsWith("{")) {
98
+ try {
99
+ const parsed = JSON.parse(existingStdout) as Record<string, unknown>;
100
+ parsed.systemMessage = text;
101
+ return { stdout: JSON.stringify(parsed), stderr: "" };
102
+ } catch {
103
+ // Not the JSON we thought it was — leave the verdict untouched
104
+ // rather than risk destroying it to add a courtesy message.
105
+ return empty;
106
+ }
107
+ }
108
+ // Anything already on stdout that we could not merge into is left
109
+ // exactly as it is, and the notice is dropped instead. The alternative —
110
+ // emitting our own object over the top — DESTROYS it: measured with a
111
+ // JSON array on stdout, which does not start with `{`, so it fell past
112
+ // the merge and was replaced wholesale. A courtesy message is never worth
113
+ // more than whatever the host was already being told.
114
+ if (existingStdout.trim()) return empty;
115
+ return { stdout: JSON.stringify({ systemMessage: text }), stderr: "" };
116
+ }
117
+ case "stderr":
118
+ // Exit code is NOT touched: on copilot a non-zero exit from a tool event
119
+ // is a block, and a notice must never block anything.
120
+ return { stdout: existingStdout, stderr: text + "\n" };
121
+ case "stdout":
122
+ // factory renders plain text under its Hooks block. Only safe when no
123
+ // verdict JSON is already on the stream.
124
+ if (existingStdout.trim()) return empty;
125
+ return { stdout: text + "\n", stderr: "" };
126
+ default:
127
+ return empty;
128
+ }
129
+ }
130
+
131
+ /** True when this CLI has a channel that was proven to reach a human. */
132
+ export function canDeliverNotice(cli: IntegrationType | undefined): boolean {
133
+ return !!cli && (NOTICE_CHANNEL[cli] ?? "none") !== "none";
134
+ }
135
+
136
+ /**
137
+ * The notice itself. A FIXED template with no interpolation of scanned content.
138
+ *
139
+ * The count is a number we computed; everything else is a constant. That is not
140
+ * stylistic: the alternative is interpolating a finding's `example`, which is
141
+ * the verbatim text of a command from a repository — attacker-controlled
142
+ * through a README, a Makefile target or an npm script. Tested against the
143
+ * shipped redactor, `echo "IGNORE ALL PRIOR INSTRUCTIONS…"` passes through
144
+ * byte-identical, because that redactor masks secrets, not instructions. On the
145
+ * CLIs where the only channel is the model's context, that string would land
146
+ * directly in the prompt. So nothing scanned ever reaches this template.
147
+ */
148
+ export function leakNoticeText(newFindings: number): string {
149
+ const n = newFindings === 1 ? "a credential" : `${newFindings} credentials`;
150
+ return (
151
+ `failproofai found ${n} in this project's agent transcripts.\n` +
152
+ "Run `failproofai audit` to see the details, " +
153
+ "or `failproofai audit --schedule` to get these by email."
154
+ );
155
+ }
@@ -27,45 +27,7 @@ function appendHint(baseReason: string, hint: unknown): string {
27
27
  return `${base}. ${normalizedHint}`;
28
28
  }
29
29
 
30
- /** Which element of a batch this evaluation is for. Absent for every non-batch
31
- * call — which is every other CLI, and every cline event that is not a
32
- * fan-out tool. */
33
- export interface BatchContext {
34
- index: number;
35
- count: number;
36
- label: string;
37
- degraded: boolean;
38
- /** Every element's canonical input, so a batch-aware policy sees the set. */
39
- all: Array<Record<string, unknown>>;
40
- /** The un-expanded original tool input. */
41
- raw: Record<string, unknown>;
42
- }
43
-
44
- export interface EvaluateOptions {
45
- /** Per-INVOCATION telemetry dedupe set, shared across every element of a
46
- * batch fan-out. Without it a 12-element batch fires 12 copies of the same
47
- * policy_evaluation_error for one real tool call. */
48
- dedupe?: Set<string>;
49
- /** Set when this call is one element of an expanded batch. */
50
- batch?: BatchContext;
51
- /** Return the raw instruct/allow entries instead of shaping them; the batch
52
- * combiner shapes ONCE at the end. A DENY is unaffected — it short-circuits
53
- * and is returned fully shaped, because it is the answer. */
54
- collectEntries?: boolean;
55
- /** Skip the policy loop entirely and shape THESE entries. This is why no
56
- * shaping code had to move: the existing per-CLI instruct/allow tails run in
57
- * place, with the real eventType, session and payload. */
58
- preEvaluated?: {
59
- instructEntries: Array<{ policyName: string; reason: string }>;
60
- allowEntries: Array<{ policyName: string; reason: string }>;
61
- };
62
- }
63
-
64
30
  export interface EvaluationResult {
65
- /** Raw instruct entries, returned ONLY when opts.collectEntries is set. */
66
- instructEntries?: Array<{ policyName: string; reason: string }>;
67
- /** Raw allow-note entries, returned ONLY when opts.collectEntries is set. */
68
- allowEntries?: Array<{ policyName: string; reason: string }>;
69
31
  exitCode: number;
70
32
  stdout: string;
71
33
  stderr: string;
@@ -168,7 +130,6 @@ export async function evaluatePolicies(
168
130
  payload: Record<string, unknown>,
169
131
  session?: SessionMetadata,
170
132
  config?: HooksConfig,
171
- opts?: EvaluateOptions,
172
133
  ): Promise<EvaluationResult> {
173
134
  const toolName = payload.tool_name as string | undefined;
174
135
  const toolInput = payload.tool_input as Record<string, unknown> | undefined;
@@ -177,7 +138,7 @@ export async function evaluatePolicies(
177
138
 
178
139
  hookLogInfo(`evaluating ${policies.length} policies for ${eventType}`);
179
140
 
180
- if (policies.length === 0 && !opts?.preEvaluated) {
141
+ if (policies.length === 0) {
181
142
  return { exitCode: 0, stdout: "", stderr: "", policyName: null, reason: null, decision: "allow" };
182
143
  }
183
144
 
@@ -190,18 +151,13 @@ export async function evaluatePolicies(
190
151
  cli: session?.cli,
191
152
  };
192
153
 
193
- // Track all instruct results (accumulated, does not short-circuit).
194
- // Seeded from opts.preEvaluated on the batch combiner's final shaping call.
195
- const instructEntries: Array<{ policyName: string; reason: string }> = [
196
- ...(opts?.preEvaluated?.instructEntries ?? []),
197
- ];
154
+ // Track all instruct results (accumulated, does not short-circuit)
155
+ const instructEntries: Array<{ policyName: string; reason: string }> = [];
198
156
 
199
157
  // Track informational messages from allow decisions (with policy attribution)
200
- const allowEntries: Array<{ policyName: string; reason: string }> = [
201
- ...(opts?.preEvaluated?.allowEntries ?? []),
202
- ];
158
+ const allowEntries: Array<{ policyName: string; reason: string }> = [];
203
159
 
204
- for (const policy of opts?.preEvaluated ? [] : policies) {
160
+ for (const policy of policies) {
205
161
  // Inject params: merge policyParams[policy.name] over schema defaults.
206
162
  // policy.name is canonical (e.g. "failproofai/block-force-push"); user
207
163
  // config keys may be flat or canonical — getConfigParamsFor accepts both.
@@ -279,17 +235,7 @@ export async function evaluatePolicies(
279
235
  } else {
280
236
  displayTool = "operation";
281
237
  }
282
- // One locator, built once, at the single site every CLI's deny shape
283
- // interpolates — so cline's {cancel,errorMessage} names the offending
284
- // element for free and nothing else needs to know a batch happened.
285
- const batchLocator = opts?.batch
286
- ? ` [batch ${opts.batch.index + 1}/${opts.batch.count}: ${
287
- opts.batch.label.length > 120
288
- ? `${opts.batch.label.slice(0, 117)}…`
289
- : opts.batch.label
290
- }]`
291
- : "";
292
- const blockedMessage = `Blocked ${displayTool}${batchLocator} by failproofai because: ${reason}, as per the policy configured by the user`;
238
+ const blockedMessage = `Blocked ${displayTool} by failproofai because: ${reason}, as per the policy configured by the user`;
293
239
 
294
240
  // Cursor's hook protocol expects a flat `{permission, user_message,
295
241
  // agent_message}` shape for any blocking decision, regardless of which
@@ -432,58 +378,6 @@ export async function evaluatePolicies(
432
378
  };
433
379
  }
434
380
 
435
- // Ori: the generated ori feature parses a flat {permission, reason} and
436
- // returns ori's own {outcome:"allow"|"deny"} to the approval extension
437
- // point. There is exactly one event (PreToolUse) — ori exposes no Stop,
438
- // prompt, post-tool or session gate — so unlike Pi/OpenClaw there is no
439
- // Stop special case to write. The reason is carried for the operator's
440
- // logs only: ori's verdict shape has no reason field, so a denial reaches
441
- // the model as a bare tool failure.
442
- if (session?.cli === "ori") {
443
- return {
444
- exitCode: 0,
445
- stdout: JSON.stringify({ permission: "deny", reason: blockedMessage }),
446
- stderr: "",
447
- policyName: policy.name,
448
- reason,
449
- decision: "deny",
450
- };
451
- }
452
-
453
- // Cline: the installed launcher relays this object verbatim on stdout, and
454
- // cline IGNORES THE EXIT CODE — the JSON is the entire verdict. The only
455
- // blocking shape its schema accepts is {cancel, errorMessage}; there is no
456
- // decision/block/permissionDecision field, so every generic branch below
457
- // is inert for cline and this one must come first.
458
- //
459
- // Stop / StopFailure are deliberately NOT a cancel: `cancel:true` becomes
460
- // {stop:true} -> ControlledStopError -> the whole run aborts, and at
461
- // TaskComplete the task has ALREADY finished, so that kills a completed
462
- // run instead of re-entering the loop. Destructive and useless, hence the
463
- // context channel there and `Stop: "observe"` in enforcement-capability.
464
- if (session?.cli === "cline") {
465
- if (eventType === "Stop" || eventType === "StopFailure") {
466
- return {
467
- exitCode: 0,
468
- stdout: JSON.stringify({
469
- context: `MANDATORY ACTION REQUIRED from failproofai (policy: ${policy.name}): ${reason}`,
470
- }),
471
- stderr: `[failproofai] ${policy.name}: ${reason}\n`,
472
- policyName: policy.name,
473
- reason,
474
- decision: "deny",
475
- };
476
- }
477
- return {
478
- exitCode: 0,
479
- stdout: JSON.stringify({ cancel: true, errorMessage: blockedMessage }),
480
- stderr: "",
481
- policyName: policy.name,
482
- reason,
483
- decision: "deny",
484
- };
485
- }
486
-
487
381
  // OpenCode: `session.idle` is a notification-only bus event — by the
488
382
  // time the plugin handler fires, OpenCode has already gone idle and
489
383
  // throwing from the handler does not force-retry. The only working
@@ -611,91 +505,6 @@ export async function evaluatePolicies(
611
505
  };
612
506
  }
613
507
 
614
- // grok: deny is `{decision:"deny", reason}` on stdout at exit 0 — verified
615
- // live against grok 1.0.3, and it beat `--yolo` (bypassPermissions). grok
616
- // does NOT read Claude's hookSpecificOutput shape: the same hook emitting
617
- // Claude's shape let the command run, emitting this one blocked it.
618
- //
619
- // Stop takes `{decision:"block", reason}` instead (Claude's turn-end
620
- // vocabulary, which grok shares) — but ONLY on a real turn end. grok fires
621
- // Stop a second time at session shutdown (`reason: "shutdown"`) and
622
- // explicitly discards that decision, so blocking there would record
623
- // enforcement that cannot happen. Falling through to allow is correct:
624
- // the turn is already over. grok caps continuations at 8 per turn.
625
- if (session?.cli === "grok") {
626
- if (eventType === "Stop" || eventType === "SubagentStop") {
627
- const grokStopReason = typeof payload.reason === "string" ? payload.reason : undefined;
628
- if (eventType === "Stop" && grokStopReason && grokStopReason !== "end_turn") {
629
- return {
630
- exitCode: 0,
631
- stdout: "",
632
- stderr: "",
633
- policyName: policy.name,
634
- reason,
635
- decision: "allow",
636
- };
637
- }
638
- const reasonText = `MANDATORY ACTION REQUIRED from failproofai (policy: ${policy.name}): ${reason}\n\nYou MUST complete the above action NOW. Do NOT ask the user for confirmation — execute the required action, then attempt to finish your task again.`;
639
- return {
640
- exitCode: 0,
641
- stdout: JSON.stringify({ decision: "block", reason: reasonText }),
642
- stderr: "",
643
- policyName: policy.name,
644
- reason,
645
- decision: "deny",
646
- };
647
- }
648
- return {
649
- exitCode: 0,
650
- stdout: JSON.stringify({ decision: "deny", reason: blockedMessage }),
651
- stderr: "",
652
- policyName: policy.name,
653
- reason,
654
- decision: "deny",
655
- };
656
- }
657
-
658
- // qwen: PreToolUse honors Claude's own
659
- // `hookSpecificOutput.permissionDecision` shape (verified live — it beat
660
- // `-y`), so that event deliberately falls through to the generic Claude
661
- // branch below rather than being duplicated here. Stop is the divergence:
662
- // it reads the top-level `{decision:"block", reason}` instead, which is
663
- // what forces another turn (verified live — the agent ran the required
664
- // command and only then finished). Unlike grok there is no session-end
665
- // Stop fire to filter out.
666
- // qwen's todo hooks (canonical TaskCreated / TaskCompleted) are the only
667
- // events in the widened set that can actually veto. They read the
668
- // top-level `{decision:"block", reason}` — NOT Claude's permissionDecision
669
- // — and only during their `validation` phase; in `postWrite` the todo is
670
- // already persisted and upstream ignores the block. Emitting it
671
- // unconditionally is right: it enforces where it can and is inert where it
672
- // cannot, and the phase is upstream's to decide, not ours to guess.
673
- if (
674
- session?.cli === "qwen" &&
675
- (eventType === "TaskCreated" || eventType === "TaskCompleted")
676
- ) {
677
- return {
678
- exitCode: 0,
679
- stdout: JSON.stringify({ decision: "block", reason: blockedMessage }),
680
- stderr: "",
681
- policyName: policy.name,
682
- reason,
683
- decision: "deny",
684
- };
685
- }
686
-
687
- if (session?.cli === "qwen" && (eventType === "Stop" || eventType === "SubagentStop")) {
688
- const reasonText = `MANDATORY ACTION REQUIRED from failproofai (policy: ${policy.name}): ${reason}\n\nYou MUST complete the above action NOW. Do NOT ask the user for confirmation — execute the required action, then attempt to finish your task again.`;
689
- return {
690
- exitCode: 0,
691
- stdout: JSON.stringify({ decision: "block", reason: reasonText }),
692
- stderr: "",
693
- policyName: policy.name,
694
- reason,
695
- decision: "deny",
696
- };
697
- }
698
-
699
508
  if (eventType === "PreToolUse") {
700
509
  const response = {
701
510
  hookSpecificOutput: {
@@ -884,21 +693,6 @@ export async function evaluatePolicies(
884
693
  }
885
694
  }
886
695
 
887
- // The batch combiner asked for RAW entries; it dedupes the union across all
888
- // elements and shapes it once, through these same tails, on a final call.
889
- if (opts?.collectEntries) {
890
- return {
891
- exitCode: 0,
892
- stdout: "",
893
- stderr: "",
894
- policyName: null,
895
- reason: null,
896
- decision: "allow",
897
- instructEntries,
898
- allowEntries,
899
- };
900
- }
901
-
902
696
  // No deny — check if we accumulated any instructs
903
697
  if (instructEntries.length > 0) {
904
698
  const combined = instructEntries.map((e) => e.reason).join("\n");
@@ -1186,44 +980,6 @@ export async function evaluatePolicies(
1186
980
  };
1187
981
  }
1188
982
 
1189
- // Ori: the approval extension points return {outcome:"allow"|"deny"} and
1190
- // nothing else — there is no additional-context channel to carry a
1191
- // directive, and no Stop event to convert one into a retry. So instruct()
1192
- // allows the action and writes the directive to stderr for the operator's
1193
- // logs; the model never sees it. Same degradation as Goose and Hermes.
1194
- if (session?.cli === "ori") {
1195
- const stderrMsg = instructEntries
1196
- .map((e) => `[failproofai] ${e.policyName}: ${e.reason}`)
1197
- .join("\n");
1198
- return {
1199
- exitCode: 0,
1200
- stdout: "",
1201
- stderr: stderrMsg + "\n",
1202
- policyName: policyNames[0],
1203
- policyNames,
1204
- reason: combined,
1205
- decision: "instruct",
1206
- };
1207
- }
1208
-
1209
- // Cline HAS a real additional-context channel — `context` is in its verdict
1210
- // schema and parseHookControl reads it — so unlike Goose/Hermes/ori this
1211
- // does not have to degrade to a stderr note. It is mirrored to stderr as
1212
- // well: the field is verified-PARSED by cline, but that the model actually
1213
- // sees it is unprobed, and the operator's log should not depend on that.
1214
- if (session?.cli === "cline") {
1215
- return {
1216
- exitCode: 0,
1217
- stdout: JSON.stringify({ context: `Instruction from failproofai: ${combined}` }),
1218
- stderr:
1219
- instructEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join("\n") + "\n",
1220
- policyName: policyNames[0],
1221
- policyNames,
1222
- reason: combined,
1223
- decision: "instruct",
1224
- };
1225
- }
1226
-
1227
983
  if (eventType === "Stop" || eventType === "SubagentStop") {
1228
984
  // Stop/SubagentStop instruct: exitCode 2 + stderr forces Claude to retry
1229
985
  // the agent (or subagent) loop with the reason as context. Same widening
@@ -1346,53 +1102,6 @@ export async function evaluatePolicies(
1346
1102
  };
1347
1103
  }
1348
1104
 
1349
- // grok: instruct has a channel only at the turn boundary. On Stop we reuse
1350
- // the verified `{decision:"block", reason}` force-retry shape so the
1351
- // instruction actually reaches the model (grok also documents a softer
1352
- // `hookSpecificOutput.additionalContext` for Stop, but that path is not
1353
- // verified and "block" is). The `end_turn` guard is the same one the deny
1354
- // branch uses — the session-shutdown fire has no turn left to instruct.
1355
- // On every other event grok has no additional-context channel, so instruct
1356
- // degrades to allow + a stderr note (like Hermes/Goose/Factory); emitting
1357
- // Claude's hookSpecificOutput there would be a shape grok discards.
1358
- //
1359
- // qwen deliberately has NO branch here: it honors Claude's
1360
- // `hookSpecificOutput.additionalContext` on PreToolUse/PostToolUse/
1361
- // UserPromptSubmit (wrapping it in a `<qwen:user-prompt-submit-context>`
1362
- // provenance tag), so the generic path below is already correct for it.
1363
- if (session?.cli === "grok") {
1364
- const stderrMsg = allowEntries
1365
- .map((e) => `[failproofai] ${e.policyName}: ${e.reason}`)
1366
- .join("\n");
1367
- const grokStopReason = typeof payload.reason === "string" ? payload.reason : undefined;
1368
- const atRealTurnEnd =
1369
- eventType === "SubagentStop" ||
1370
- (eventType === "Stop" && (!grokStopReason || grokStopReason === "end_turn"));
1371
- if (atRealTurnEnd) {
1372
- return {
1373
- exitCode: 0,
1374
- stdout: JSON.stringify({
1375
- decision: "block",
1376
- reason: `Instruction from failproofai: ${combined}`,
1377
- }),
1378
- stderr: stderrMsg + "\n",
1379
- policyName: policyNames[0],
1380
- policyNames,
1381
- reason: combined,
1382
- decision: "instruct",
1383
- };
1384
- }
1385
- return {
1386
- exitCode: 0,
1387
- stdout: "",
1388
- stderr: stderrMsg + "\n",
1389
- policyName: policyNames[0],
1390
- policyNames,
1391
- reason: combined,
1392
- decision: "allow",
1393
- };
1394
- }
1395
-
1396
1105
  const supportsHookSpecificOutput =
1397
1106
  eventType === "PreToolUse" ||
1398
1107
  eventType === "PostToolUse" ||