failproofai 1.0.3 → 1.0.4-beta.1

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 (251) hide show
  1. package/.next/standalone/.grok/hooks/failproofai.json +172 -0
  2. package/.next/standalone/.next/BUILD_ID +1 -1
  3. package/.next/standalone/.next/build-manifest.json +5 -5
  4. package/.next/standalone/.next/prerender-manifest.json +4 -4
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  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 +6 -6
  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/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  22. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  23. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +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/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  33. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  35. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  36. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/index.html +1 -1
  39. package/.next/standalone/.next/server/app/index.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  41. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  42. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  43. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  44. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  45. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  49. package/.next/standalone/.next/server/app/policies/page.js +1 -1
  50. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
  55. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  58. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  64. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  65. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  66. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  67. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  69. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  70. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/chunks/{[externals]__0u3wrvq._.js → [externals]__0_gzk8q._.js} +1 -1
  73. package/.next/standalone/.next/server/chunks/{[externals]__0-9ti7z._.js → [externals]__1zndo1i._.js} +1 -1
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07hor7h._.js +5 -0
  75. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0elsuli._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0gnj_qa._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljuzm_._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w23bpg._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0x5c7sh._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yik9_w._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1jg6s9m._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1s6qci9._.js +3 -0
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xfm05z._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/_0tdy_6l._.js +3 -0
  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 +21 -14
  93. package/.next/standalone/.next/server/chunks/lib_grok-projects_ts_1i2epl_._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/lib_qwen-projects_ts_0wu_a8e._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  96. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  97. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_4gcz8._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0d--wmy._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
  105. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.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]__0qqrlf_._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s11fx1._.js +3 -0
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
  111. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0n_lxhg._.js → [root-of-the-server]__0wvusu0._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1b5jzxr._.js → [root-of-the-server]__16gamwq._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bbk-si._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1c3ybxr._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1izi7zn._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zl_r0t._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__21bj7uw._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +1 -1
  121. package/.next/standalone/.next/server/chunks/ssr/{_0v8eebd._.js → _00z5uuq._.js} +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/{_1if9ixi._.js → _08f0jd2._.js} +1 -1
  123. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/_0wso5d3._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/{_0j6gd_k._.js → _1byck5v._.js} +2 -2
  126. package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +155 -1
  127. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  130. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/lib_cline-projects_ts_13_hbdd._.js +3 -0
  135. package/.next/standalone/.next/server/chunks/ssr/lib_grok-projects_ts_0rc-d0o._.js +3 -0
  136. package/.next/standalone/.next/server/chunks/ssr/lib_ori-projects_ts_04tofqo._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/lib_qwen-projects_ts_0w3i9o7._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
  139. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  140. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  141. package/.next/standalone/.next/server/pages/404.html +1 -1
  142. package/.next/standalone/.next/server/pages/500.html +1 -1
  143. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  144. package/.next/standalone/.next/server/server-reference-manifest.json +19 -19
  145. package/.next/standalone/.next/static/chunks/{0ollg-nnu3t3e.js → 00gv0v91mycm-.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/{354-n7p7labpn.js → 01hvwwrhvbzyh.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/0hzmxwb2pvk5p.js +6 -0
  148. package/.next/standalone/.next/static/chunks/{0nm11ysn0153h.js → 0j884dwnwtyrt.js} +1 -1
  149. package/.next/standalone/.next/static/chunks/0mr9dg3bnloa1.js +1 -0
  150. package/.next/standalone/.next/static/chunks/0qn9khv0oo9ex.js +1 -0
  151. package/.next/standalone/.next/static/chunks/{2xm5mj1d21b4n.js → 1_2jsatfuthrw.js} +1 -1
  152. package/.next/standalone/.next/static/chunks/1_bxotmf9qngs.js +1 -0
  153. package/.next/standalone/.next/static/chunks/1eyl7fzkmnkf_.js +1 -0
  154. package/.next/standalone/.next/static/chunks/{42yfkhekx440s.js → 1po3lirgk2riw.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{40lpt2o2b283e.js → 2bnf_4pgyk1rq.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{1a6u7l3giejch.js → 2bvwsyu6x__ia.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{3l36d73kj18zw.js → 3_--3ibia6ba1.js} +1 -1
  158. package/.next/standalone/.next/static/chunks/3gf1jg-5m-u1t.css +2 -0
  159. package/.next/standalone/.next/static/chunks/{3pibxnbhjza7n.js → 3ws7eljkq25wg.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/{turbopack-2x-o099q64i8h.js → turbopack-18qf4jodtft6b.js} +1 -1
  161. package/.next/standalone/.qwen/settings.json +251 -0
  162. package/.next/standalone/app/project/[name]/page.tsx +24 -4
  163. package/.next/standalone/assets/logos/cline.png +0 -0
  164. package/.next/standalone/assets/logos/grok-dark.svg +1 -0
  165. package/.next/standalone/assets/logos/grok-light.svg +1 -0
  166. package/.next/standalone/assets/logos/ori-dark.svg +1 -0
  167. package/.next/standalone/assets/logos/ori-light.svg +1 -0
  168. package/.next/standalone/assets/logos/qwen.svg +1 -0
  169. package/.next/standalone/lib/cli-registry.ts +33 -1
  170. package/.next/standalone/lib/cline-projects.ts +155 -0
  171. package/.next/standalone/lib/cline-sessions.ts +268 -0
  172. package/.next/standalone/lib/download-session.ts +37 -0
  173. package/.next/standalone/lib/grok-projects.ts +123 -0
  174. package/.next/standalone/lib/grok-sessions.ts +355 -0
  175. package/.next/standalone/lib/ori-projects.ts +141 -0
  176. package/.next/standalone/lib/ori-sessions.ts +221 -0
  177. package/.next/standalone/lib/projects.ts +27 -3
  178. package/.next/standalone/lib/qwen-projects.ts +106 -0
  179. package/.next/standalone/lib/qwen-sessions.ts +313 -0
  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/app-page-turbo-experimental.runtime.prod.js +1 -1
  183. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +1 -1
  184. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +1 -1
  185. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +5 -5
  186. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  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/incremental-cache/file-system-cache.js +17 -4
  193. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  194. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/escape-path-delimiters.js +1 -1
  195. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  196. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  197. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  198. package/.next/standalone/node_modules/next/package.json +10 -10
  199. package/.next/standalone/package.json +11 -11
  200. package/.next/standalone/sdk/python/uv.lock +29 -29
  201. package/.next/standalone/server.js +1 -1
  202. package/README.md +38 -7
  203. package/bin/failproofai.mjs +24 -7
  204. package/dist/cli.mjs +20313 -17756
  205. package/dist/worker.mjs +8521 -6768
  206. package/lib/cli-registry.ts +33 -1
  207. package/lib/cline-projects.ts +155 -0
  208. package/lib/cline-sessions.ts +268 -0
  209. package/lib/download-session.ts +37 -0
  210. package/lib/grok-projects.ts +123 -0
  211. package/lib/grok-sessions.ts +355 -0
  212. package/lib/ori-projects.ts +141 -0
  213. package/lib/ori-sessions.ts +221 -0
  214. package/lib/projects.ts +27 -3
  215. package/lib/qwen-projects.ts +106 -0
  216. package/lib/qwen-sessions.ts +313 -0
  217. package/package.json +11 -11
  218. package/src/audit/cli-adapters/cline.ts +50 -0
  219. package/src/audit/cli-adapters/grok.ts +70 -0
  220. package/src/audit/cli-adapters/index.ts +24 -0
  221. package/src/audit/cli-adapters/ori.ts +55 -0
  222. package/src/audit/cli-adapters/qwen.ts +71 -0
  223. package/src/hooks/batch-expand.ts +380 -0
  224. package/src/hooks/batch-fanout.ts +142 -0
  225. package/src/hooks/builtin-policies.ts +6 -4
  226. package/src/hooks/enforcement-capability.ts +97 -0
  227. package/src/hooks/handler.ts +61 -5
  228. package/src/hooks/harness-cli.ts +3 -1
  229. package/src/hooks/integrations.ts +756 -2
  230. package/src/hooks/normalize-cli-payload.ts +126 -0
  231. package/src/hooks/policy-evaluator.ts +297 -6
  232. package/src/hooks/resolve-transcript-path.ts +10 -0
  233. package/src/hooks/risk-patterns.ts +26 -0
  234. package/src/hooks/tool-name-canonicalize.ts +64 -0
  235. package/src/hooks/types.ts +533 -2
  236. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +0 -3
  237. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +0 -5
  238. package/.next/standalone/.next/server/chunks/_1-akg6w._.js +0 -3
  239. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00-s7h8._.js +0 -3
  240. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__190won8._.js +0 -3
  241. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
  242. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1sc84bb._.js +0 -3
  243. package/.next/standalone/.next/static/chunks/03slu0c8_71y7.js +0 -1
  244. package/.next/standalone/.next/static/chunks/1-_zkmnm-jwei.js +0 -1
  245. package/.next/standalone/.next/static/chunks/1r9676mkvihto.js +0 -1
  246. package/.next/standalone/.next/static/chunks/30pct5s734jua.js +0 -6
  247. package/.next/standalone/.next/static/chunks/40nr6ehj4p3cw.css +0 -2
  248. package/.next/standalone/.next/static/chunks/41b5ptaocvi_l.js +0 -1
  249. /package/.next/standalone/.next/static/{JBYgkXkA1PS48m9P_hKNw → qJx3mbOQV-5ElrcRb4Sp7}/_buildManifest.js +0 -0
  250. /package/.next/standalone/.next/static/{JBYgkXkA1PS48m9P_hKNw → qJx3mbOQV-5ElrcRb4Sp7}/_clientMiddlewareManifest.js +0 -0
  251. /package/.next/standalone/.next/static/{JBYgkXkA1PS48m9P_hKNw → qJx3mbOQV-5ElrcRb4Sp7}/_ssgManifest.js +0 -0
@@ -7,7 +7,7 @@
7
7
  * is agent-agnostic — only install/uninstall plumbing varies.
8
8
  */
9
9
  import { execSync } from "node:child_process";
10
- import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
10
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, rmSync, chmodSync } from "node:fs";
11
11
  import { resolve, dirname } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { homedir } from "node:os";
@@ -38,6 +38,14 @@ import {
38
38
  ANTIGRAVITY_HOOK_EVENT_TYPES,
39
39
  ANTIGRAVITY_HOOK_SCOPES,
40
40
  GOOSE_HOOK_EVENT_TYPES,
41
+ GROK_HOOK_EVENT_TYPES,
42
+ GROK_HOOK_SCOPES,
43
+ QWEN_HOOK_EVENT_TYPES,
44
+ QWEN_HOOK_SCOPES,
45
+ ORI_HOOK_SCOPES,
46
+ ORI_HOOK_EVENT_TYPES,
47
+ CLINE_HOOK_SCOPES,
48
+ CLINE_HOOK_EVENT_TYPES,
41
49
  GOOSE_HOOK_SCOPES,
42
50
  FAILPROOFAI_HOOK_MARKER,
43
51
  INTEGRATION_TYPES,
@@ -2275,13 +2283,755 @@ export const goose: Integration = {
2275
2283
  },
2276
2284
  };
2277
2285
 
2278
- // ── Registry ────────────────────────────────────────────────────────────────
2286
+
2287
+ // ── Ori (OpenRouter's ori) ──────────────────────────────────────────────────
2288
+ //
2289
+ // Enforces on ori's OWN agent (bare `ori` / `ori code`), NOT on its launcher
2290
+ // path — `ori claude`, `ori codex`, `ori grok`, `ori opencode` and friends run
2291
+ // the real third-party binary with credentials injected and no config-dir
2292
+ // redirect, so the hooks already installed for those CLIs keep enforcing there
2293
+ // with nothing ori-specific required. See the ORI section of types.ts for the
2294
+ // live evidence behind that.
2295
+ //
2296
+ // A feature is a workspace package under the global workspace's `features/`,
2297
+ // AUTO-DISCOVERED with no config file to register it in — so, like Goose, the
2298
+ // install is "drop a directory" rather than "edit a settings file". The
2299
+ // bookkeeping JSON below exists purely so the Integration interface has a
2300
+ // settings path to read, write and detect against; ori itself ignores it.
2301
+ //
2302
+ // USER scope only: bare `ori` boots the GLOBAL workspace rather than the
2303
+ // project's, so one install covers every project and a project-scope install
2304
+ // would never be loaded.
2305
+ export const ORI_FEATURE_ID = "failproofai";
2306
+
2307
+ /** ori's home (override with ORI_HOME, used by tests). */
2308
+ export function oriHome(): string {
2309
+ return process.env.ORI_HOME || resolve(homedir(), ".ori");
2310
+ }
2311
+
2312
+ /** The feature directory failproofai generates inside ori's global workspace. */
2313
+ export function oriFeatureDir(): string {
2314
+ return resolve(oriHome(), "global", "features", ORI_FEATURE_ID);
2315
+ }
2316
+
2317
+ /**
2318
+ * The ori feature shim.
2319
+ *
2320
+ * Claims all three published approval extension points. Each is `unique`, so
2321
+ * leaving one unclaimed invites another feature to take it — but the STATIC one
2322
+ * is deliberately inert (`defaultAction: "ask"`, which is what self-drive
2323
+ * already does). Claiming it with a blanket `reject` would brick every
2324
+ * self-drive session the moment failproofai is installed.
2325
+ *
2326
+ * FAIL-CLOSED BY DEFAULT, and this is the one integration where that is free:
2327
+ * ori declares `failureBehavior: "deny"` on both dynamic points — "a throwing,
2328
+ * rejecting, or malformed provider denies the request" — so letting an error
2329
+ * propagate IS the deny. Set FAILPROOFAI_ORI_FAIL_OPEN=1 to allow instead, for
2330
+ * anyone who would rather a failproofai fault not stop their agent.
2331
+ */
2332
+ function buildOriFeatureShim(binaryPath: string): string {
2333
+ return `// AUTO-GENERATED by failproofai. ${FAILPROOFAI_HOOK_MARKER}
2334
+ // Re-generate via: failproofai policies --install --cli ori
2335
+ //
2336
+ // Bridges ori's approval extension points to the failproofai binary. Ships NO
2337
+ // inline tool maps: it forwards ori's raw tool name and arguments, and the
2338
+ // binary canonicalizes them via ORI_TOOL_MAP / ORI_TOOL_INPUT_MAP (one source
2339
+ // of truth), exactly as the OpenClaw plugin does.
2340
+ import { spawn } from "node:child_process";
2341
+
2342
+ import { ApprovalAskers, ApprovalPolicies, UnattendedApprovals } from "ori";
2343
+
2344
+ const BIN = ${JSON.stringify(binaryPath)};
2345
+ const FAIL_OPEN = process.env.FAILPROOFAI_ORI_FAIL_OPEN === "1";
2346
+
2347
+ /** ori hands arguments as a flat {name, value} string array. Rebuild the object
2348
+ * the binary's tool-input canonicalization expects. The synthetic
2349
+ * \`escalated\` entry ori appends on the second pass of its escalation ladder
2350
+ * is metadata, not a tool argument, so it is dropped from the tool input. */
2351
+ function toToolInput(request) {
2352
+ const out = {};
2353
+ for (const a of request.arguments ?? []) {
2354
+ if (request.escalated && a.name === "escalated" && a.value === "true") continue;
2355
+ out[a.name] = a.value;
2356
+ }
2357
+ return out;
2358
+ }
2359
+
2360
+ function callPolicy(request) {
2361
+ return new Promise((done, fail) => {
2362
+ let child;
2363
+ try {
2364
+ child = spawn("node", [BIN, "--hook", "PreToolUse", "--cli", "ori"], {
2365
+ stdio: ["pipe", "pipe", "inherit"],
2366
+ });
2367
+ } catch (err) {
2368
+ FAIL_OPEN ? done({ permission: "allow" }) : fail(err);
2369
+ return;
2370
+ }
2371
+ let out = "";
2372
+ let settled = false;
2373
+ const finish = (fn, arg) => {
2374
+ if (settled) return;
2375
+ settled = true;
2376
+ clearTimeout(timer);
2377
+ fn(arg);
2378
+ };
2379
+ const timer = setTimeout(() => {
2380
+ try { child.kill(); } catch { /* already gone */ }
2381
+ FAIL_OPEN
2382
+ ? finish(done, { permission: "allow" })
2383
+ : finish(fail, new Error("failproofai timed out"));
2384
+ }, 30_000);
2385
+ child.stdout.on("data", (d) => { out += d; });
2386
+ child.on("error", (err) => {
2387
+ FAIL_OPEN ? finish(done, { permission: "allow" }) : finish(fail, err);
2388
+ });
2389
+ child.on("close", (code, signal) => {
2390
+ // A non-zero exit or a signal means failproofai produced NO verdict — a
2391
+ // missing bundle, a crashed evaluator, an OOM kill. Resolving that to
2392
+ // "allow" is exactly the silent non-enforcement ori's
2393
+ // failureBehavior deny exists to prevent, and it could never fire,
2394
+ // because resolving is what tells ori the provider succeeded. Only a
2395
+ // REJECT reaches that guarantee.
2396
+ if (signal || (typeof code === "number" && code !== 0)) {
2397
+ const err = new Error(
2398
+ "failproofai produced no verdict (" +
2399
+ (signal ? "killed by " + signal : "exit code " + code) +
2400
+ ")",
2401
+ );
2402
+ FAIL_OPEN ? finish(done, { permission: "allow" }) : finish(fail, err);
2403
+ return;
2404
+ }
2405
+ const stdout = out.trim();
2406
+ // Exit 0 with empty stdout IS the evaluator's clean-allow shape — the one
2407
+ // case where no output is a real verdict rather than a failure.
2408
+ if (!stdout) { finish(done, { permission: "allow" }); return; }
2409
+ let verdict;
2410
+ try {
2411
+ verdict = JSON.parse(stdout);
2412
+ } catch (err) {
2413
+ FAIL_OPEN ? finish(done, { permission: "allow" }) : finish(fail, err);
2414
+ return;
2415
+ }
2416
+ // Anything that is not an explicit allow/deny is an unrecognised answer,
2417
+ // not an allow. decide() maps every non-"deny" to allow, so an
2418
+ // unvalidated object would silently become one.
2419
+ if (!verdict || (verdict.permission !== "allow" && verdict.permission !== "deny")) {
2420
+ const err = new Error("failproofai returned an unrecognised verdict: " + stdout.slice(0, 200));
2421
+ FAIL_OPEN ? finish(done, { permission: "allow" }) : finish(fail, err);
2422
+ return;
2423
+ }
2424
+ finish(done, verdict);
2425
+ });
2426
+ try {
2427
+ child.stdin.end(JSON.stringify({
2428
+ hook_event_name: "PreToolUse",
2429
+ tool_name: request.tool,
2430
+ tool_input: toToolInput(request),
2431
+ cwd: process.cwd(),
2432
+ ori: {
2433
+ capabilities: request.capabilities ?? [],
2434
+ escalated: request.escalated === true,
2435
+ },
2436
+ }));
2437
+ } catch (err) {
2438
+ FAIL_OPEN ? finish(done, { permission: "allow" }) : finish(fail, err);
2439
+ }
2440
+ });
2441
+ }
2442
+
2443
+ async function decide(request) {
2444
+ const verdict = await callPolicy(request);
2445
+ return { outcome: verdict.permission === "deny" ? "deny" : "allow" };
2446
+ }
2447
+
2448
+ export const provisions = [
2449
+ // Claimed so no other feature can take it; inert on purpose — see above.
2450
+ ApprovalPolicies.provide({ defaultAction: "ask", rules: [] }),
2451
+ // The gate that fires when a human is present (\`ori --approvals manual\`).
2452
+ ApprovalAskers.provide(decide),
2453
+ // The gate that fires on unattended runs.
2454
+ UnattendedApprovals.provide(decide),
2455
+ ];
2456
+ `;
2457
+ }
2458
+
2459
+ const ORI_FEATURE_PACKAGE_JSON = `${JSON.stringify(
2460
+ { name: "failproofai", private: true, type: "module" },
2461
+ null,
2462
+ 2,
2463
+ )}\n`;
2464
+
2465
+ export const ori: Integration = {
2466
+ id: "ori",
2467
+ displayName: "Ori",
2468
+ scopes: ORI_HOOK_SCOPES,
2469
+ eventTypes: ORI_HOOK_EVENT_TYPES,
2470
+
2471
+ // USER scope only — ori has no project workspace that bare `ori` would boot.
2472
+ // The path is bookkeeping for this interface, not something ori reads.
2473
+ getSettingsPath() {
2474
+ return resolve(oriFeatureDir(), "failproofai.json");
2475
+ },
2476
+
2477
+ readSettings(settingsPath) {
2478
+ return readJsonFile(settingsPath);
2479
+ },
2480
+
2481
+ writeSettings(settingsPath, settings) {
2482
+ writeJsonFile(settingsPath, settings);
2483
+ },
2484
+
2485
+ buildHookEntry(binaryPath, eventType) {
2486
+ return { type: "command", command: `"${binaryPath}" --hook ${eventType} --cli ori`, [FAILPROOFAI_HOOK_MARKER]: true };
2487
+ },
2488
+
2489
+ isFailproofaiHook(hook) {
2490
+ if (!hook || typeof hook !== "object") return false;
2491
+ return (hook as Record<string, unknown>)[FAILPROOFAI_HOOK_MARKER] === true;
2492
+ },
2493
+
2494
+ /**
2495
+ * Install: write the feature (feature.ts + package.json) into ori's global
2496
+ * workspace, and record the entry in the bookkeeping JSON. ori discovers the
2497
+ * directory on its own — there is no config array to append to.
2498
+ */
2499
+ writeHookEntries(settings, binaryPath) {
2500
+ const dir = oriFeatureDir();
2501
+ mkdirSync(dir, { recursive: true });
2502
+ writeFileSync(resolve(dir, "feature.ts"), buildOriFeatureShim(binaryPath), "utf8");
2503
+ writeFileSync(resolve(dir, "package.json"), ORI_FEATURE_PACKAGE_JSON, "utf8");
2504
+
2505
+ const s = settings as Record<string, unknown>;
2506
+ s.hooks = ORI_HOOK_EVENT_TYPES.map((eventType) =>
2507
+ this.buildHookEntry(binaryPath, eventType, "user"),
2508
+ );
2509
+ },
2510
+
2511
+ /**
2512
+ * Uninstall: drop the whole feature directory, but ONLY when the generated
2513
+ * feature carries our marker — never delete a hand-written ori feature that
2514
+ * happens to sit at the same path.
2515
+ */
2516
+ removeHooksFromFile(settingsPath) {
2517
+ const dir = dirname(settingsPath);
2518
+ const featurePath = resolve(dir, "feature.ts");
2519
+ let removed = 0;
2520
+
2521
+ const settings = this.readSettings(settingsPath) as Record<string, unknown>;
2522
+ if (Array.isArray(settings.hooks)) {
2523
+ removed = settings.hooks.filter((h) => this.isFailproofaiHook(h)).length;
2524
+ }
2525
+
2526
+ const ours =
2527
+ existsSync(featurePath) &&
2528
+ readFileSync(featurePath, "utf8").includes(FAILPROOFAI_HOOK_MARKER);
2529
+ if (ours) {
2530
+ rmSync(dir, { recursive: true, force: true });
2531
+ return removed || 1;
2532
+ }
2533
+
2534
+ // Someone replaced the feature by hand — leave it, drop only our record.
2535
+ delete settings.hooks;
2536
+ this.writeSettings(settingsPath, settings);
2537
+ return removed;
2538
+ },
2539
+
2540
+ hooksInstalledInSettings() {
2541
+ const featurePath = resolve(oriFeatureDir(), "feature.ts");
2542
+ if (!existsSync(featurePath)) return false;
2543
+ try {
2544
+ return readFileSync(featurePath, "utf8").includes(FAILPROOFAI_HOOK_MARKER);
2545
+ } catch {
2546
+ return false;
2547
+ }
2548
+ },
2549
+
2550
+ detectInstalled() {
2551
+ return binaryExists("ori");
2552
+ },
2553
+ };
2554
+
2555
+
2556
+ // ── Cline (cline CLI) ───────────────────────────────────────────────────────
2557
+ //
2558
+ // Cline's config is a DIRECTORY OF EVENT-NAMED FILES, not a settings file — the
2559
+ // only integration shaped that way. There is no JSON to merge and no array to
2560
+ // append to: the install drops one launcher script per event, named exactly for
2561
+ // the event it subscribes to.
2562
+ //
2563
+ // Two rules from the live contract govern the whole install (cline v3.0.60):
2564
+ // 1. FILENAME == EVENT, case-insensitively, and only ten names exist. A typo
2565
+ // is not an error, it is SILENCE — which is exactly how an earlier probe
2566
+ // concluded, wrongly, that cline had no reachable hook surface.
2567
+ // 2. The EXTENSION must be in cline's allowlist or the file is skipped, also
2568
+ // silently. We write `.sh` with a matching shebang so the interpreter is
2569
+ // unambiguous whichever rule cline applies (shebang first, else extension).
2570
+ //
2571
+ // THE CRITICAL DIFFERENCE FROM ORI: ori owns its entire feature directory, so
2572
+ // uninstall can remove it wholesale. Cline's hooks directory is SHARED with the
2573
+ // user's own hooks, so uninstall deletes individual files carrying our marker
2574
+ // and MUST NEVER remove the directory — deleting someone's hand-written
2575
+ // `PreToolUse.py` because it sat at the same path is unrecoverable.
2576
+ //
2577
+ // NEVER emit `--hooks-dir`: that flag writes CLINE_HOOKS_DIR, which appears
2578
+ // exactly once in cline's shipped binary — the write — and is read by nothing.
2579
+
2580
+ /** The extension we write. In cline's allowlist, and paired with a shebang. */
2581
+ export const CLINE_HOOK_FILE_EXTENSION = ".sh";
2582
+
2583
+ /** cline's home (CLINE_DIR is cline's own var; CLINE_HOME is for tests). */
2584
+ export function clineIntegrationHome(): string {
2585
+ return process.env.CLINE_HOME || process.env.CLINE_DIR || resolve(homedir(), ".cline");
2586
+ }
2587
+
2588
+ /**
2589
+ * cline scans four directories; we write the two that map onto our scopes.
2590
+ * User scope is `$CLINE_DIR/hooks`; project scope is `<cwd>/.clinerules/hooks`.
2591
+ */
2592
+ export function clineHooksDir(scope: HookScope, cwd?: string): string {
2593
+ const base = cwd ? resolve(cwd) : process.cwd();
2594
+ return scope === "user"
2595
+ ? resolve(clineIntegrationHome(), "hooks")
2596
+ : resolve(base, ".clinerules", "hooks");
2597
+ }
2598
+
2599
+ function buildClineHookScript(binaryPath: string, eventType: string, scope: HookScope): string {
2600
+ const invocation = scope === "project" ? "npx -y failproofai" : `"${binaryPath}"`;
2601
+ return `#!/usr/bin/env bash
2602
+ # AUTO-GENERATED by failproofai. ${FAILPROOFAI_HOOK_MARKER}
2603
+ # Re-generate via: failproofai policies --install --cli cline
2604
+ #
2605
+ # THE FILENAME IS THE EVENT. cline binds this file to its ${eventType} event by
2606
+ # basename (case-insensitively) and by nothing else: renaming it silently
2607
+ # unsubscribes it, and an extension outside cline's allowlist is skipped without
2608
+ # a log line. Verified against cline v3.0.60.
2609
+ #
2610
+ # cline IGNORES THE EXIT CODE. The verdict is the single JSON object on stdout,
2611
+ # so this script must ALWAYS print exactly one object, and must print '{}' when
2612
+ # failproofai produced nothing: unparseable stdout makes cline skip the hook and
2613
+ # run the tool anyway (fail-open, 120s default timeout).
2614
+ #
2615
+ # Blocking shape, the only one cline reads: {"cancel":true,"errorMessage":"..."}.
2616
+ # There is no decision/block/permissionDecision field in cline's schema.
2617
+ set -u
2618
+
2619
+ payload="$(cat)"
2620
+
2621
+ # stderr is deliberately NOT redirected: failproofai's diagnostics belong in
2622
+ # cline's own hook log, and stdout stays clean because the evaluator writes
2623
+ # every operator note to stderr.
2624
+ verdict="$(printf '%s' "$payload" | ${invocation} --hook ${eventType} --cli cline)"
2625
+
2626
+ if [ -z "$verdict" ]; then
2627
+ verdict='{}'
2628
+ fi
2629
+ printf '%s\\n' "$verdict"
2630
+ exit 0
2631
+ `;
2632
+ }
2633
+
2634
+ export const cline: Integration = {
2635
+ id: "cline",
2636
+ displayName: "Cline",
2637
+ scopes: CLINE_HOOK_SCOPES,
2638
+ eventTypes: CLINE_HOOK_EVENT_TYPES,
2639
+
2640
+ getSettingsPath(scope, cwd) {
2641
+ // Bookkeeping only, so the Integration interface has something to read,
2642
+ // write and detect against. Inert for cline twice over: `.json` is not in
2643
+ // the extension allowlist AND `failproofai` is not one of the ten event
2644
+ // names, so cline never executes it.
2645
+ return resolve(
2646
+ clineHooksDir(scope === "local" ? "project" : scope, cwd),
2647
+ "failproofai.json",
2648
+ );
2649
+ },
2650
+
2651
+ readSettings(settingsPath) {
2652
+ return readJsonFile(settingsPath);
2653
+ },
2654
+
2655
+ writeSettings(settingsPath, settings) {
2656
+ writeJsonFile(settingsPath, settings);
2657
+ },
2658
+
2659
+ buildHookEntry(binaryPath, eventType, scope) {
2660
+ const effective: HookScope = scope === "local" ? "project" : (scope ?? "user");
2661
+ return {
2662
+ type: "command",
2663
+ file: `${eventType}${CLINE_HOOK_FILE_EXTENSION}`,
2664
+ command: `${
2665
+ effective === "project" ? "npx -y failproofai" : `"${binaryPath}"`
2666
+ } --hook ${eventType} --cli cline`,
2667
+ [FAILPROOFAI_HOOK_MARKER]: true,
2668
+ };
2669
+ },
2670
+
2671
+ isFailproofaiHook(hook) {
2672
+ if (!hook || typeof hook !== "object") return false;
2673
+ return (hook as Record<string, unknown>)[FAILPROOFAI_HOOK_MARKER] === true;
2674
+ },
2675
+
2676
+ writeHookEntries(settings, binaryPath, scope) {
2677
+ const effective: HookScope = scope === "local" ? "project" : (scope ?? "user");
2678
+ const dir = clineHooksDir(effective);
2679
+ mkdirSync(dir, { recursive: true });
2680
+ for (const eventType of CLINE_HOOK_EVENT_TYPES) {
2681
+ const file = resolve(dir, `${eventType}${CLINE_HOOK_FILE_EXTENSION}`);
2682
+ // Refuse to clobber a hook we did not write. A hand-written PreToolUse.sh
2683
+ // is someone's enforcement too, and overwriting it is the same class of
2684
+ // damage as deleting it.
2685
+ if (existsSync(file)) {
2686
+ let existing = "";
2687
+ try {
2688
+ existing = readFileSync(file, "utf8");
2689
+ } catch {
2690
+ /* unreadable — treat as foreign and leave alone */
2691
+ continue;
2692
+ }
2693
+ if (!existing.includes(FAILPROOFAI_HOOK_MARKER)) continue;
2694
+ }
2695
+ writeFileSync(file, buildClineHookScript(binaryPath, eventType, effective), "utf8");
2696
+ // cline does not require the executable bit (it picks an interpreter from
2697
+ // the shebang or the extension), but set it anyway: harmless, and it keeps
2698
+ // the file usable if cline ever execs directly.
2699
+ try {
2700
+ chmodSync(file, 0o755);
2701
+ } catch {
2702
+ /* best effort */
2703
+ }
2704
+ }
2705
+ const s = settings as Record<string, unknown>;
2706
+ s.hooks = CLINE_HOOK_EVENT_TYPES.map((e) => this.buildHookEntry(binaryPath, e, effective));
2707
+ },
2708
+
2709
+ removeHooksFromFile(settingsPath) {
2710
+ const dir = dirname(settingsPath);
2711
+ let removed = 0;
2712
+ for (const eventType of CLINE_HOOK_EVENT_TYPES) {
2713
+ const file = resolve(dir, `${eventType}${CLINE_HOOK_FILE_EXTENSION}`);
2714
+ if (!existsSync(file)) continue;
2715
+ let body = "";
2716
+ try {
2717
+ body = readFileSync(file, "utf8");
2718
+ } catch {
2719
+ continue;
2720
+ }
2721
+ if (!body.includes(FAILPROOFAI_HOOK_MARKER)) continue; // not ours — leave it
2722
+ try {
2723
+ unlinkSync(file);
2724
+ removed++;
2725
+ } catch {
2726
+ /* already gone */
2727
+ }
2728
+ }
2729
+ // NEVER remove `dir`. Unlike ori's feature directory, this one is SHARED
2730
+ // with the user's own hooks.
2731
+ const settings = this.readSettings(settingsPath) as Record<string, unknown>;
2732
+ delete settings.hooks;
2733
+ this.writeSettings(settingsPath, settings);
2734
+ return removed;
2735
+ },
2736
+
2737
+ hooksInstalledInSettings(scope, cwd) {
2738
+ const file = resolve(
2739
+ clineHooksDir(scope === "local" ? "project" : scope, cwd),
2740
+ `PreToolUse${CLINE_HOOK_FILE_EXTENSION}`,
2741
+ );
2742
+ if (!existsSync(file)) return false;
2743
+ try {
2744
+ return readFileSync(file, "utf8").includes(FAILPROOFAI_HOOK_MARKER);
2745
+ } catch {
2746
+ return false;
2747
+ }
2748
+ },
2749
+
2750
+ detectInstalled() {
2751
+ return binaryExists("cline");
2752
+ },
2753
+ };
2754
+
2755
+ // ── Registry ──────────────────────────────────────────────────────────────
2279
2756
 
2280
2757
  // `Partial` is kept (not every IntegrationType is guaranteed installable for
2281
2758
  // LIVE hooks / Pillar 1) so a future audit-only CLI can omit its entry without a
2282
2759
  // type error. `hermes` now has BOTH an audit adapter
2283
2760
  // (src/audit/cli-adapters/hermes.ts) AND live-hook install support, so it is
2284
2761
  // registered here.
2762
+ // ── grok (xAI grok CLI) integration ─────────────────────────────────────────
2763
+ //
2764
+ // grok reads Claude's NESTED hook schema out of its own directory: every
2765
+ // `*.json` under `~/.grok/hooks/` (user, always trusted) or
2766
+ // `<repo>/.grok/hooks/` (project). We own one file, `failproofai.json`, so the
2767
+ // install never has to merge with a user's other hook files — the same
2768
+ // arrangement as Copilot's `.github/hooks/failproofai.json`.
2769
+ //
2770
+ // `timeout` is in SECONDS (grok's default is 5, which is too tight for a cold
2771
+ // `npx` start; 30 matches what the other second-based CLIs use here).
2772
+ //
2773
+ // TWO project-scope conditions that are NOT in grok's docs and were found by
2774
+ // probing grok 1.0.3 — both silently produce a config that never fires:
2775
+ // • the directory must be a GIT REPO (a trusted non-git dir logs
2776
+ // `project_sources=0`), and
2777
+ // • the folder must be TRUSTED (`grok --trust` or `/hooks-trust`).
2778
+ // `projectScopeWarning()` below reports the first; the second is surfaced in
2779
+ // the install output, since neither has any other detector.
2780
+ //
2781
+ // Deny/instruct semantics live in policy-evaluator.ts's `cli === "grok"` branch
2782
+ // (grok's OWN `{decision:"deny"}` / `{decision:"block"}` shapes — it ignores
2783
+ // Claude's hookSpecificOutput deny, verified by A/B on a live session).
2784
+ const grok: Integration = {
2785
+ id: "grok",
2786
+ displayName: "grok CLI",
2787
+ scopes: GROK_HOOK_SCOPES,
2788
+ eventTypes: GROK_HOOK_EVENT_TYPES,
2789
+
2790
+ getSettingsPath(scope, cwd) {
2791
+ const base = cwd ? resolve(cwd) : process.cwd();
2792
+ switch (scope) {
2793
+ case "user":
2794
+ return resolve(homedir(), ".grok", "hooks", "failproofai.json");
2795
+ case "project":
2796
+ case "local":
2797
+ // grok has no "local" scope; fall back to project so callers don't crash.
2798
+ return resolve(base, ".grok", "hooks", "failproofai.json");
2799
+ }
2800
+ },
2801
+
2802
+ readSettings(settingsPath) {
2803
+ return readJsonFile(settingsPath);
2804
+ },
2805
+
2806
+ writeSettings(settingsPath, settings) {
2807
+ writeJsonFile(settingsPath, settings);
2808
+ },
2809
+
2810
+ buildHookEntry(binaryPath, eventType, scope) {
2811
+ const command =
2812
+ scope === "project"
2813
+ ? `npx -y failproofai --hook ${eventType} --cli grok`
2814
+ : `"${binaryPath}" --hook ${eventType} --cli grok`;
2815
+ return {
2816
+ type: "command",
2817
+ command,
2818
+ // grok reads `timeout` in SECONDS. Its own default is 5s (600s for
2819
+ // Stop/SubagentStop); 30 leaves room for a cold `npx` start without
2820
+ // stalling the UI, and a timed-out hook fails OPEN on grok.
2821
+ timeout: 30,
2822
+ [FAILPROOFAI_HOOK_MARKER]: true,
2823
+ };
2824
+ },
2825
+
2826
+ isFailproofaiHook: isMarkedHook,
2827
+
2828
+ writeHookEntries(settings, binaryPath, scope) {
2829
+ const s = settings as ClaudeSettings;
2830
+ if (!s.hooks) s.hooks = {};
2831
+
2832
+ for (const eventType of GROK_HOOK_EVENT_TYPES) {
2833
+ const hookEntry = this.buildHookEntry(binaryPath, eventType, scope) as unknown as ClaudeHookEntry;
2834
+ if (!s.hooks[eventType]) s.hooks[eventType] = [];
2835
+ const matchers: ClaudeHookMatcher[] = s.hooks[eventType];
2836
+
2837
+ let found = false;
2838
+ for (const matcher of matchers) {
2839
+ if (!matcher.hooks) continue;
2840
+ const idx = matcher.hooks.findIndex((h) => isMarkedHook(h as Record<string, unknown>));
2841
+ if (idx >= 0) {
2842
+ matcher.hooks[idx] = hookEntry;
2843
+ found = true;
2844
+ break;
2845
+ }
2846
+ }
2847
+ // No `matcher` key: an omitted matcher matches every tool. Do NOT write
2848
+ // `"*"` here — grok treats a matcher as a REGEX, and the same bare `"*"`
2849
+ // is an invalid pattern that matches nothing on Goose.
2850
+ if (!found) matchers.push({ hooks: [hookEntry] });
2851
+ }
2852
+ },
2853
+
2854
+ removeHooksFromFile(settingsPath) {
2855
+ const settings = this.readSettings(settingsPath) as ClaudeSettings;
2856
+ if (!settings.hooks) return 0;
2857
+
2858
+ let removed = 0;
2859
+ for (const eventType of Object.keys(settings.hooks)) {
2860
+ const matchers = settings.hooks[eventType];
2861
+ if (!Array.isArray(matchers)) continue;
2862
+ for (let i = matchers.length - 1; i >= 0; i--) {
2863
+ const matcher = matchers[i];
2864
+ if (!matcher.hooks) continue;
2865
+ const before = matcher.hooks.length;
2866
+ matcher.hooks = matcher.hooks.filter((h) => !isMarkedHook(h as Record<string, unknown>));
2867
+ removed += before - matcher.hooks.length;
2868
+ if (matcher.hooks.length === 0) matchers.splice(i, 1);
2869
+ }
2870
+ if (matchers.length === 0) delete settings.hooks[eventType];
2871
+ }
2872
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
2873
+
2874
+ this.writeSettings(settingsPath, settings as Record<string, unknown>);
2875
+ return removed;
2876
+ },
2877
+
2878
+ hooksInstalledInSettings(scope, cwd) {
2879
+ const settingsPath = this.getSettingsPath(scope, cwd);
2880
+ if (!existsSync(settingsPath)) return false;
2881
+ try {
2882
+ const settings = this.readSettings(settingsPath) as ClaudeSettings;
2883
+ if (!settings.hooks) return false;
2884
+ for (const matchers of Object.values(settings.hooks)) {
2885
+ if (!Array.isArray(matchers)) continue;
2886
+ for (const matcher of matchers) {
2887
+ if (!matcher.hooks) continue;
2888
+ if (matcher.hooks.some((h) => isMarkedHook(h as Record<string, unknown>))) return true;
2889
+ }
2890
+ }
2891
+ } catch {
2892
+ // Corrupt settings — treat as not installed
2893
+ }
2894
+ return false;
2895
+ },
2896
+
2897
+ detectInstalled() {
2898
+ return binaryExists("grok");
2899
+ },
2900
+ };
2901
+
2902
+ // ── qwen (Qwen Code) integration ────────────────────────────────────────────
2903
+ //
2904
+ // qwen keeps hooks under a Claude-style `"hooks"` key inside its normal
2905
+ // settings file, which also holds `model`, `modelProviders`, auth, etc. — so
2906
+ // reads/writes go through the merge-preserving readJsonFile/writeJsonFile
2907
+ // helpers (like Claude/Copilot/Devin), never a whole-file replace.
2908
+ // user → ~/.qwen/settings.json
2909
+ // project → <cwd>/.qwen/settings.json
2910
+ //
2911
+ // The one shape difference from every other integration: `timeout` is in
2912
+ // MILLISECONDS (qwen's default is 60000). Do not "unify" it with the
2913
+ // seconds-based CLIs — 30 would mean 30ms and every hook would time out.
2914
+ //
2915
+ // Deny is Claude's own `hookSpecificOutput.permissionDecision` on PreToolUse
2916
+ // (so no evaluator branch is needed for it); only Stop diverges. See the
2917
+ // `cli === "qwen"` branch in policy-evaluator.ts.
2918
+ const qwen: Integration = {
2919
+ id: "qwen",
2920
+ displayName: "Qwen Code",
2921
+ scopes: QWEN_HOOK_SCOPES,
2922
+ eventTypes: QWEN_HOOK_EVENT_TYPES,
2923
+
2924
+ getSettingsPath(scope, cwd) {
2925
+ const base = cwd ? resolve(cwd) : process.cwd();
2926
+ switch (scope) {
2927
+ case "user":
2928
+ return resolve(homedir(), ".qwen", "settings.json");
2929
+ case "project":
2930
+ case "local":
2931
+ // qwen has no "local" scope; fall back to project so callers don't crash.
2932
+ return resolve(base, ".qwen", "settings.json");
2933
+ }
2934
+ },
2935
+
2936
+ readSettings(settingsPath) {
2937
+ return readJsonFile(settingsPath);
2938
+ },
2939
+
2940
+ writeSettings(settingsPath, settings) {
2941
+ writeJsonFile(settingsPath, settings);
2942
+ },
2943
+
2944
+ buildHookEntry(binaryPath, eventType, scope) {
2945
+ const command =
2946
+ scope === "project"
2947
+ ? `npx -y failproofai --hook ${eventType} --cli qwen`
2948
+ : `"${binaryPath}" --hook ${eventType} --cli qwen`;
2949
+ return {
2950
+ type: "command",
2951
+ command,
2952
+ name: `failproofai-${eventType}`,
2953
+ // MILLISECONDS — qwen is the only integration that is not seconds-based.
2954
+ // 30000 = 30s, matching the wall-clock budget the others use.
2955
+ timeout: 30000,
2956
+ [FAILPROOFAI_HOOK_MARKER]: true,
2957
+ };
2958
+ },
2959
+
2960
+ isFailproofaiHook: isMarkedHook,
2961
+
2962
+ writeHookEntries(settings, binaryPath, scope) {
2963
+ const s = settings as ClaudeSettings;
2964
+ if (!s.hooks) s.hooks = {};
2965
+
2966
+ for (const eventType of QWEN_HOOK_EVENT_TYPES) {
2967
+ const hookEntry = this.buildHookEntry(binaryPath, eventType, scope) as unknown as ClaudeHookEntry;
2968
+ if (!s.hooks[eventType]) s.hooks[eventType] = [];
2969
+ const matchers: ClaudeHookMatcher[] = s.hooks[eventType];
2970
+
2971
+ let found = false;
2972
+ for (const matcher of matchers) {
2973
+ if (!matcher.hooks) continue;
2974
+ const idx = matcher.hooks.findIndex((h) => isMarkedHook(h as Record<string, unknown>));
2975
+ if (idx >= 0) {
2976
+ matcher.hooks[idx] = hookEntry;
2977
+ found = true;
2978
+ break;
2979
+ }
2980
+ }
2981
+ // Matcher omitted = match every tool. qwen also accepts `""`/`"*"`, but an
2982
+ // omitted key is the one form that is unambiguous across all of them.
2983
+ if (!found) matchers.push({ hooks: [hookEntry] });
2984
+ }
2985
+ },
2986
+
2987
+ removeHooksFromFile(settingsPath) {
2988
+ const settings = this.readSettings(settingsPath) as ClaudeSettings;
2989
+ if (!settings.hooks) return 0;
2990
+
2991
+ let removed = 0;
2992
+ for (const eventType of Object.keys(settings.hooks)) {
2993
+ const matchers = settings.hooks[eventType];
2994
+ if (!Array.isArray(matchers)) continue;
2995
+ for (let i = matchers.length - 1; i >= 0; i--) {
2996
+ const matcher = matchers[i];
2997
+ if (!matcher.hooks) continue;
2998
+ const before = matcher.hooks.length;
2999
+ matcher.hooks = matcher.hooks.filter((h) => !isMarkedHook(h as Record<string, unknown>));
3000
+ removed += before - matcher.hooks.length;
3001
+ if (matcher.hooks.length === 0) matchers.splice(i, 1);
3002
+ }
3003
+ if (matchers.length === 0) delete settings.hooks[eventType];
3004
+ }
3005
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
3006
+
3007
+ this.writeSettings(settingsPath, settings as Record<string, unknown>);
3008
+ return removed;
3009
+ },
3010
+
3011
+ hooksInstalledInSettings(scope, cwd) {
3012
+ const settingsPath = this.getSettingsPath(scope, cwd);
3013
+ if (!existsSync(settingsPath)) return false;
3014
+ try {
3015
+ const settings = this.readSettings(settingsPath) as ClaudeSettings;
3016
+ if (!settings.hooks) return false;
3017
+ for (const matchers of Object.values(settings.hooks)) {
3018
+ if (!Array.isArray(matchers)) continue;
3019
+ for (const matcher of matchers) {
3020
+ if (!matcher.hooks) continue;
3021
+ if (matcher.hooks.some((h) => isMarkedHook(h as Record<string, unknown>))) return true;
3022
+ }
3023
+ }
3024
+ } catch {
3025
+ // Corrupt settings — treat as not installed
3026
+ }
3027
+ return false;
3028
+ },
3029
+
3030
+ detectInstalled() {
3031
+ return binaryExists("qwen");
3032
+ },
3033
+ };
3034
+
2285
3035
  const INTEGRATIONS: Partial<Record<IntegrationType, Integration>> = {
2286
3036
  claude: claudeCode,
2287
3037
  codex,
@@ -2295,6 +3045,10 @@ const INTEGRATIONS: Partial<Record<IntegrationType, Integration>> = {
2295
3045
  devin,
2296
3046
  antigravity,
2297
3047
  goose,
3048
+ grok,
3049
+ qwen,
3050
+ ori,
3051
+ cline,
2298
3052
  };
2299
3053
 
2300
3054
  export function getIntegration(id: IntegrationType): Integration {