failproofai 0.0.2 → 0.0.4-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (484) hide show
  1. package/.next/standalone/.claude/settings.json +316 -0
  2. package/.next/standalone/.failproofai/policies/workflow-policies.mjs +62 -0
  3. package/.next/standalone/.failproofai/policies-config.json +39 -0
  4. package/.next/standalone/.next/BUILD_ID +1 -1
  5. package/.next/standalone/.next/build-manifest.json +5 -5
  6. package/.next/standalone/.next/prerender-manifest.json +3 -3
  7. package/.next/standalone/.next/required-server-files.json +3 -1
  8. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  9. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  16. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  20. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
  25. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
  26. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  27. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  30. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  31. package/.next/standalone/.next/server/app/index.html +1 -1
  32. package/.next/standalone/.next/server/app/index.rsc +16 -16
  33. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  34. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
  35. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  36. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
  37. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  38. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  39. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  40. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  44. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  47. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  48. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  51. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  52. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  57. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +3 -0
  60. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kjo7d_._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_05pz9._._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qo8503._.js → [root-of-the-server]__0jqus-j._.js} +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +9 -9
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__12kr5~_._.js → [root-of-the-server]__131id~1._.js} +2 -2
  73. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  74. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0a_7sdg.js +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0ef3uwk.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0j79~gv.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0pbja1x.js +2 -2
  80. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0r6o0i2.js +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_11y81~_.js +2 -2
  82. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_12or2kf.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js +1 -1
  84. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  85. package/.next/standalone/.next/server/pages/404.html +2 -2
  86. package/.next/standalone/.next/server/pages/500.html +1 -1
  87. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  89. package/.next/standalone/.next/static/chunks/{0y~0creqvl5wx.js → 045lpk_isd5np.js} +1 -1
  90. package/.next/standalone/.next/static/chunks/{0cvffh-pbsv5u.js → 065qrrpfkts8s.js} +1 -1
  91. package/.next/standalone/.next/static/chunks/{031pa5~qfzt~_.js → 09e7drilkf1sn.js} +1 -1
  92. package/.next/standalone/.next/static/chunks/0gu_a.a80ritd.css +1 -0
  93. package/.next/standalone/.next/static/chunks/{15wf7x-e.8ia3.js → 0je_~y72wv~~2.js} +1 -1
  94. package/.next/standalone/.next/static/chunks/{0x-625~1vx1lu.js → 0rqcttnl9u32c.js} +1 -1
  95. package/.next/standalone/.next/static/chunks/{0ov60i6md~37t.js → 0v2-.v07.zb9u.js} +2 -2
  96. package/.next/standalone/.next/static/chunks/{06og.7e9nkpjh.js → 0yye9-w._6rz~.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/{0_4y_t03jn2nq.js → 15proylk5ye2k.js} +1 -1
  98. package/.next/standalone/.next/static/chunks/174boqk9e~20i.js +6 -0
  99. package/.next/standalone/.next/static/chunks/{turbopack-0uc5y~g6h.n7-.js → turbopack-0r26pc8h0y_-e.js} +1 -1
  100. package/.next/standalone/CHANGELOG.md +108 -0
  101. package/.next/standalone/CLAUDE.md +28 -0
  102. package/.next/standalone/Dockerfile.docs +12 -0
  103. package/.next/standalone/README.md +95 -49
  104. package/.next/standalone/app/components/session-hooks-panel.tsx +14 -1
  105. package/.next/standalone/app/policies/hooks-client.tsx +14 -1
  106. package/.next/standalone/bin/failproofai.mjs +5 -0
  107. package/.next/standalone/bun.lock +76 -63
  108. package/.next/standalone/components/navbar.tsx +5 -0
  109. package/.next/standalone/dist/cli.mjs +535 -90
  110. package/.next/standalone/dist/index.js +2 -2
  111. package/.next/standalone/docs/ar/architecture.mdx +333 -0
  112. package/.next/standalone/docs/ar/built-in-policies.mdx +537 -0
  113. package/.next/standalone/docs/ar/cli/dashboard.mdx +28 -0
  114. package/.next/standalone/docs/ar/cli/environment-variables.mdx +34 -0
  115. package/.next/standalone/docs/ar/cli/hook.mdx +31 -0
  116. package/.next/standalone/docs/ar/cli/install-policies.mdx +48 -0
  117. package/.next/standalone/docs/ar/cli/list-policies.mdx +31 -0
  118. package/.next/standalone/docs/ar/cli/remove-policies.mdx +43 -0
  119. package/.next/standalone/docs/ar/cli/version.mdx +13 -0
  120. package/.next/standalone/docs/ar/configuration.mdx +223 -0
  121. package/.next/standalone/docs/ar/custom-policies.mdx +359 -0
  122. package/.next/standalone/docs/ar/dashboard.mdx +142 -0
  123. package/.next/standalone/docs/ar/examples.mdx +254 -0
  124. package/.next/standalone/docs/ar/for-agents.mdx +39 -0
  125. package/.next/standalone/docs/ar/getting-started.mdx +134 -0
  126. package/.next/standalone/docs/ar/introduction.mdx +58 -0
  127. package/.next/standalone/docs/ar/package-aliases.mdx +82 -0
  128. package/.next/standalone/docs/ar/testing.mdx +261 -0
  129. package/.next/standalone/docs/{architecture.md → architecture.mdx} +40 -23
  130. package/.next/standalone/docs/{built-in-policies.md → built-in-policies.mdx} +126 -15
  131. package/.next/standalone/docs/cli/dashboard.mdx +28 -0
  132. package/.next/standalone/docs/cli/environment-variables.mdx +34 -0
  133. package/.next/standalone/docs/cli/hook.mdx +30 -0
  134. package/.next/standalone/docs/cli/install-policies.mdx +47 -0
  135. package/.next/standalone/docs/cli/list-policies.mdx +31 -0
  136. package/.next/standalone/docs/cli/remove-policies.mdx +43 -0
  137. package/.next/standalone/docs/cli/version.mdx +12 -0
  138. package/.next/standalone/docs/{configuration.md → configuration.mdx} +62 -16
  139. package/.next/standalone/docs/custom-policies.mdx +357 -0
  140. package/.next/standalone/docs/{dashboard.md → dashboard.mdx} +26 -29
  141. package/.next/standalone/docs/de/architecture.mdx +332 -0
  142. package/.next/standalone/docs/de/built-in-policies.mdx +537 -0
  143. package/.next/standalone/docs/de/cli/dashboard.mdx +28 -0
  144. package/.next/standalone/docs/de/cli/environment-variables.mdx +34 -0
  145. package/.next/standalone/docs/de/cli/hook.mdx +30 -0
  146. package/.next/standalone/docs/de/cli/install-policies.mdx +47 -0
  147. package/.next/standalone/docs/de/cli/list-policies.mdx +31 -0
  148. package/.next/standalone/docs/de/cli/remove-policies.mdx +43 -0
  149. package/.next/standalone/docs/de/cli/version.mdx +12 -0
  150. package/.next/standalone/docs/de/configuration.mdx +222 -0
  151. package/.next/standalone/docs/de/custom-policies.mdx +357 -0
  152. package/.next/standalone/docs/de/dashboard.mdx +142 -0
  153. package/.next/standalone/docs/de/examples.mdx +253 -0
  154. package/.next/standalone/docs/de/for-agents.mdx +38 -0
  155. package/.next/standalone/docs/de/getting-started.mdx +134 -0
  156. package/.next/standalone/docs/de/introduction.mdx +57 -0
  157. package/.next/standalone/docs/de/package-aliases.mdx +82 -0
  158. package/.next/standalone/docs/de/testing.mdx +260 -0
  159. package/.next/standalone/docs/docs.json +943 -24
  160. package/.next/standalone/docs/es/architecture.mdx +332 -0
  161. package/.next/standalone/docs/es/built-in-policies.mdx +537 -0
  162. package/.next/standalone/docs/es/cli/dashboard.mdx +28 -0
  163. package/.next/standalone/docs/es/cli/environment-variables.mdx +34 -0
  164. package/.next/standalone/docs/es/cli/hook.mdx +30 -0
  165. package/.next/standalone/docs/es/cli/install-policies.mdx +47 -0
  166. package/.next/standalone/docs/es/cli/list-policies.mdx +31 -0
  167. package/.next/standalone/docs/es/cli/remove-policies.mdx +43 -0
  168. package/.next/standalone/docs/es/cli/version.mdx +12 -0
  169. package/.next/standalone/docs/es/configuration.mdx +222 -0
  170. package/.next/standalone/docs/es/custom-policies.mdx +357 -0
  171. package/.next/standalone/docs/es/dashboard.mdx +142 -0
  172. package/.next/standalone/docs/es/examples.mdx +253 -0
  173. package/.next/standalone/docs/es/for-agents.mdx +38 -0
  174. package/.next/standalone/docs/es/getting-started.mdx +134 -0
  175. package/.next/standalone/docs/es/introduction.mdx +57 -0
  176. package/.next/standalone/docs/es/package-aliases.mdx +82 -0
  177. package/.next/standalone/docs/es/testing.mdx +260 -0
  178. package/.next/standalone/docs/examples.mdx +253 -0
  179. package/.next/standalone/docs/for-agents.mdx +38 -0
  180. package/.next/standalone/docs/fr/architecture.mdx +332 -0
  181. package/.next/standalone/docs/fr/built-in-policies.mdx +537 -0
  182. package/.next/standalone/docs/fr/cli/dashboard.mdx +28 -0
  183. package/.next/standalone/docs/fr/cli/environment-variables.mdx +34 -0
  184. package/.next/standalone/docs/fr/cli/hook.mdx +30 -0
  185. package/.next/standalone/docs/fr/cli/install-policies.mdx +47 -0
  186. package/.next/standalone/docs/fr/cli/list-policies.mdx +31 -0
  187. package/.next/standalone/docs/fr/cli/remove-policies.mdx +43 -0
  188. package/.next/standalone/docs/fr/cli/version.mdx +12 -0
  189. package/.next/standalone/docs/fr/configuration.mdx +222 -0
  190. package/.next/standalone/docs/fr/custom-policies.mdx +357 -0
  191. package/.next/standalone/docs/fr/dashboard.mdx +142 -0
  192. package/.next/standalone/docs/fr/examples.mdx +253 -0
  193. package/.next/standalone/docs/fr/for-agents.mdx +38 -0
  194. package/.next/standalone/docs/fr/getting-started.mdx +134 -0
  195. package/.next/standalone/docs/fr/introduction.mdx +57 -0
  196. package/.next/standalone/docs/fr/package-aliases.mdx +82 -0
  197. package/.next/standalone/docs/fr/testing.mdx +260 -0
  198. package/.next/standalone/docs/getting-started.mdx +134 -0
  199. package/.next/standalone/docs/he/architecture.mdx +333 -0
  200. package/.next/standalone/docs/he/built-in-policies.mdx +535 -0
  201. package/.next/standalone/docs/he/cli/dashboard.mdx +28 -0
  202. package/.next/standalone/docs/he/cli/environment-variables.mdx +34 -0
  203. package/.next/standalone/docs/he/cli/hook.mdx +30 -0
  204. package/.next/standalone/docs/he/cli/install-policies.mdx +47 -0
  205. package/.next/standalone/docs/he/cli/list-policies.mdx +32 -0
  206. package/.next/standalone/docs/he/cli/remove-policies.mdx +43 -0
  207. package/.next/standalone/docs/he/cli/version.mdx +12 -0
  208. package/.next/standalone/docs/he/configuration.mdx +222 -0
  209. package/.next/standalone/docs/he/custom-policies.mdx +357 -0
  210. package/.next/standalone/docs/he/dashboard.mdx +142 -0
  211. package/.next/standalone/docs/he/examples.mdx +253 -0
  212. package/.next/standalone/docs/he/for-agents.mdx +38 -0
  213. package/.next/standalone/docs/he/getting-started.mdx +135 -0
  214. package/.next/standalone/docs/he/introduction.mdx +57 -0
  215. package/.next/standalone/docs/he/package-aliases.mdx +82 -0
  216. package/.next/standalone/docs/he/testing.mdx +260 -0
  217. package/.next/standalone/docs/hi/architecture.mdx +334 -0
  218. package/.next/standalone/docs/hi/built-in-policies.mdx +535 -0
  219. package/.next/standalone/docs/hi/cli/dashboard.mdx +28 -0
  220. package/.next/standalone/docs/hi/cli/environment-variables.mdx +34 -0
  221. package/.next/standalone/docs/hi/cli/hook.mdx +30 -0
  222. package/.next/standalone/docs/hi/cli/install-policies.mdx +47 -0
  223. package/.next/standalone/docs/hi/cli/list-policies.mdx +31 -0
  224. package/.next/standalone/docs/hi/cli/remove-policies.mdx +43 -0
  225. package/.next/standalone/docs/hi/cli/version.mdx +12 -0
  226. package/.next/standalone/docs/hi/configuration.mdx +222 -0
  227. package/.next/standalone/docs/hi/custom-policies.mdx +357 -0
  228. package/.next/standalone/docs/hi/dashboard.mdx +142 -0
  229. package/.next/standalone/docs/hi/examples.mdx +255 -0
  230. package/.next/standalone/docs/hi/for-agents.mdx +38 -0
  231. package/.next/standalone/docs/hi/getting-started.mdx +134 -0
  232. package/.next/standalone/docs/hi/introduction.mdx +57 -0
  233. package/.next/standalone/docs/hi/package-aliases.mdx +82 -0
  234. package/.next/standalone/docs/hi/testing.mdx +260 -0
  235. package/.next/standalone/docs/i18n/README.ar.md +312 -0
  236. package/.next/standalone/docs/i18n/README.de.md +307 -0
  237. package/.next/standalone/docs/i18n/README.es.md +307 -0
  238. package/.next/standalone/docs/i18n/README.fr.md +307 -0
  239. package/.next/standalone/docs/i18n/README.he.md +312 -0
  240. package/.next/standalone/docs/i18n/README.hi.md +307 -0
  241. package/.next/standalone/docs/i18n/README.it.md +307 -0
  242. package/.next/standalone/docs/i18n/README.ja.md +307 -0
  243. package/.next/standalone/docs/i18n/README.ko.md +307 -0
  244. package/.next/standalone/docs/i18n/README.pt-br.md +307 -0
  245. package/.next/standalone/docs/i18n/README.ru.md +308 -0
  246. package/.next/standalone/docs/i18n/README.tr.md +308 -0
  247. package/.next/standalone/docs/i18n/README.vi.md +308 -0
  248. package/.next/standalone/docs/i18n/README.zh.md +307 -0
  249. package/.next/standalone/docs/introduction.mdx +57 -0
  250. package/.next/standalone/docs/it/architecture.mdx +333 -0
  251. package/.next/standalone/docs/it/built-in-policies.mdx +537 -0
  252. package/.next/standalone/docs/it/cli/dashboard.mdx +28 -0
  253. package/.next/standalone/docs/it/cli/environment-variables.mdx +34 -0
  254. package/.next/standalone/docs/it/cli/hook.mdx +30 -0
  255. package/.next/standalone/docs/it/cli/install-policies.mdx +47 -0
  256. package/.next/standalone/docs/it/cli/list-policies.mdx +31 -0
  257. package/.next/standalone/docs/it/cli/remove-policies.mdx +43 -0
  258. package/.next/standalone/docs/it/cli/version.mdx +12 -0
  259. package/.next/standalone/docs/it/configuration.mdx +223 -0
  260. package/.next/standalone/docs/it/custom-policies.mdx +358 -0
  261. package/.next/standalone/docs/it/dashboard.mdx +142 -0
  262. package/.next/standalone/docs/it/examples.mdx +253 -0
  263. package/.next/standalone/docs/it/for-agents.mdx +38 -0
  264. package/.next/standalone/docs/it/getting-started.mdx +134 -0
  265. package/.next/standalone/docs/it/introduction.mdx +57 -0
  266. package/.next/standalone/docs/it/package-aliases.mdx +82 -0
  267. package/.next/standalone/docs/it/testing.mdx +260 -0
  268. package/.next/standalone/docs/ja/architecture.mdx +332 -0
  269. package/.next/standalone/docs/ja/built-in-policies.mdx +535 -0
  270. package/.next/standalone/docs/ja/cli/dashboard.mdx +28 -0
  271. package/.next/standalone/docs/ja/cli/environment-variables.mdx +34 -0
  272. package/.next/standalone/docs/ja/cli/hook.mdx +30 -0
  273. package/.next/standalone/docs/ja/cli/install-policies.mdx +47 -0
  274. package/.next/standalone/docs/ja/cli/list-policies.mdx +31 -0
  275. package/.next/standalone/docs/ja/cli/remove-policies.mdx +43 -0
  276. package/.next/standalone/docs/ja/cli/version.mdx +12 -0
  277. package/.next/standalone/docs/ja/configuration.mdx +222 -0
  278. package/.next/standalone/docs/ja/custom-policies.mdx +357 -0
  279. package/.next/standalone/docs/ja/dashboard.mdx +142 -0
  280. package/.next/standalone/docs/ja/examples.mdx +253 -0
  281. package/.next/standalone/docs/ja/for-agents.mdx +38 -0
  282. package/.next/standalone/docs/ja/getting-started.mdx +134 -0
  283. package/.next/standalone/docs/ja/introduction.mdx +57 -0
  284. package/.next/standalone/docs/ja/package-aliases.mdx +82 -0
  285. package/.next/standalone/docs/ja/testing.mdx +260 -0
  286. package/.next/standalone/docs/ko/architecture.mdx +332 -0
  287. package/.next/standalone/docs/ko/built-in-policies.mdx +535 -0
  288. package/.next/standalone/docs/ko/cli/dashboard.mdx +28 -0
  289. package/.next/standalone/docs/ko/cli/environment-variables.mdx +34 -0
  290. package/.next/standalone/docs/ko/cli/hook.mdx +30 -0
  291. package/.next/standalone/docs/ko/cli/install-policies.mdx +47 -0
  292. package/.next/standalone/docs/ko/cli/list-policies.mdx +31 -0
  293. package/.next/standalone/docs/ko/cli/remove-policies.mdx +43 -0
  294. package/.next/standalone/docs/ko/cli/version.mdx +12 -0
  295. package/.next/standalone/docs/ko/configuration.mdx +222 -0
  296. package/.next/standalone/docs/ko/custom-policies.mdx +357 -0
  297. package/.next/standalone/docs/ko/dashboard.mdx +142 -0
  298. package/.next/standalone/docs/ko/examples.mdx +253 -0
  299. package/.next/standalone/docs/ko/for-agents.mdx +38 -0
  300. package/.next/standalone/docs/ko/getting-started.mdx +134 -0
  301. package/.next/standalone/docs/ko/introduction.mdx +57 -0
  302. package/.next/standalone/docs/ko/package-aliases.mdx +82 -0
  303. package/.next/standalone/docs/ko/testing.mdx +260 -0
  304. package/.next/standalone/docs/logo/dark.svg +21 -0
  305. package/.next/standalone/docs/logo/light.svg +21 -0
  306. package/.next/standalone/docs/{package-aliases.md → package-aliases.mdx} +5 -5
  307. package/.next/standalone/docs/pt-br/architecture.mdx +332 -0
  308. package/.next/standalone/docs/pt-br/built-in-policies.mdx +537 -0
  309. package/.next/standalone/docs/pt-br/cli/dashboard.mdx +28 -0
  310. package/.next/standalone/docs/pt-br/cli/environment-variables.mdx +34 -0
  311. package/.next/standalone/docs/pt-br/cli/hook.mdx +30 -0
  312. package/.next/standalone/docs/pt-br/cli/install-policies.mdx +47 -0
  313. package/.next/standalone/docs/pt-br/cli/list-policies.mdx +31 -0
  314. package/.next/standalone/docs/pt-br/cli/remove-policies.mdx +43 -0
  315. package/.next/standalone/docs/pt-br/cli/version.mdx +12 -0
  316. package/.next/standalone/docs/pt-br/configuration.mdx +222 -0
  317. package/.next/standalone/docs/pt-br/custom-policies.mdx +357 -0
  318. package/.next/standalone/docs/pt-br/dashboard.mdx +142 -0
  319. package/.next/standalone/docs/pt-br/examples.mdx +253 -0
  320. package/.next/standalone/docs/pt-br/for-agents.mdx +38 -0
  321. package/.next/standalone/docs/pt-br/getting-started.mdx +134 -0
  322. package/.next/standalone/docs/pt-br/introduction.mdx +57 -0
  323. package/.next/standalone/docs/pt-br/package-aliases.mdx +82 -0
  324. package/.next/standalone/docs/pt-br/testing.mdx +260 -0
  325. package/.next/standalone/docs/ru/architecture.mdx +334 -0
  326. package/.next/standalone/docs/ru/built-in-policies.mdx +537 -0
  327. package/.next/standalone/docs/ru/cli/dashboard.mdx +28 -0
  328. package/.next/standalone/docs/ru/cli/environment-variables.mdx +34 -0
  329. package/.next/standalone/docs/ru/cli/hook.mdx +30 -0
  330. package/.next/standalone/docs/ru/cli/install-policies.mdx +48 -0
  331. package/.next/standalone/docs/ru/cli/list-policies.mdx +32 -0
  332. package/.next/standalone/docs/ru/cli/remove-policies.mdx +43 -0
  333. package/.next/standalone/docs/ru/cli/version.mdx +12 -0
  334. package/.next/standalone/docs/ru/configuration.mdx +223 -0
  335. package/.next/standalone/docs/ru/custom-policies.mdx +357 -0
  336. package/.next/standalone/docs/ru/dashboard.mdx +142 -0
  337. package/.next/standalone/docs/ru/examples.mdx +254 -0
  338. package/.next/standalone/docs/ru/for-agents.mdx +38 -0
  339. package/.next/standalone/docs/ru/getting-started.mdx +134 -0
  340. package/.next/standalone/docs/ru/introduction.mdx +57 -0
  341. package/.next/standalone/docs/ru/package-aliases.mdx +82 -0
  342. package/.next/standalone/docs/ru/testing.mdx +260 -0
  343. package/.next/standalone/docs/{testing.md → testing.mdx} +11 -11
  344. package/.next/standalone/docs/tr/architecture.mdx +333 -0
  345. package/.next/standalone/docs/tr/built-in-policies.mdx +537 -0
  346. package/.next/standalone/docs/tr/cli/dashboard.mdx +28 -0
  347. package/.next/standalone/docs/tr/cli/environment-variables.mdx +34 -0
  348. package/.next/standalone/docs/tr/cli/hook.mdx +30 -0
  349. package/.next/standalone/docs/tr/cli/install-policies.mdx +47 -0
  350. package/.next/standalone/docs/tr/cli/list-policies.mdx +31 -0
  351. package/.next/standalone/docs/tr/cli/remove-policies.mdx +44 -0
  352. package/.next/standalone/docs/tr/cli/version.mdx +12 -0
  353. package/.next/standalone/docs/tr/configuration.mdx +223 -0
  354. package/.next/standalone/docs/tr/custom-policies.mdx +357 -0
  355. package/.next/standalone/docs/tr/dashboard.mdx +142 -0
  356. package/.next/standalone/docs/tr/examples.mdx +253 -0
  357. package/.next/standalone/docs/tr/for-agents.mdx +38 -0
  358. package/.next/standalone/docs/tr/getting-started.mdx +134 -0
  359. package/.next/standalone/docs/tr/introduction.mdx +57 -0
  360. package/.next/standalone/docs/tr/package-aliases.mdx +82 -0
  361. package/.next/standalone/docs/tr/testing.mdx +260 -0
  362. package/.next/standalone/docs/vi/architecture.mdx +333 -0
  363. package/.next/standalone/docs/vi/built-in-policies.mdx +537 -0
  364. package/.next/standalone/docs/vi/cli/dashboard.mdx +28 -0
  365. package/.next/standalone/docs/vi/cli/environment-variables.mdx +34 -0
  366. package/.next/standalone/docs/vi/cli/hook.mdx +30 -0
  367. package/.next/standalone/docs/vi/cli/install-policies.mdx +47 -0
  368. package/.next/standalone/docs/vi/cli/list-policies.mdx +31 -0
  369. package/.next/standalone/docs/vi/cli/remove-policies.mdx +43 -0
  370. package/.next/standalone/docs/vi/cli/version.mdx +13 -0
  371. package/.next/standalone/docs/vi/configuration.mdx +222 -0
  372. package/.next/standalone/docs/vi/custom-policies.mdx +357 -0
  373. package/.next/standalone/docs/vi/dashboard.mdx +142 -0
  374. package/.next/standalone/docs/vi/examples.mdx +253 -0
  375. package/.next/standalone/docs/vi/for-agents.mdx +38 -0
  376. package/.next/standalone/docs/vi/getting-started.mdx +134 -0
  377. package/.next/standalone/docs/vi/introduction.mdx +57 -0
  378. package/.next/standalone/docs/vi/package-aliases.mdx +82 -0
  379. package/.next/standalone/docs/vi/testing.mdx +260 -0
  380. package/.next/standalone/docs/zh/architecture.mdx +332 -0
  381. package/.next/standalone/docs/zh/built-in-policies.mdx +535 -0
  382. package/.next/standalone/docs/zh/cli/dashboard.mdx +28 -0
  383. package/.next/standalone/docs/zh/cli/environment-variables.mdx +34 -0
  384. package/.next/standalone/docs/zh/cli/hook.mdx +30 -0
  385. package/.next/standalone/docs/zh/cli/install-policies.mdx +47 -0
  386. package/.next/standalone/docs/zh/cli/list-policies.mdx +31 -0
  387. package/.next/standalone/docs/zh/cli/remove-policies.mdx +43 -0
  388. package/.next/standalone/docs/zh/cli/version.mdx +12 -0
  389. package/.next/standalone/docs/zh/configuration.mdx +222 -0
  390. package/.next/standalone/docs/zh/custom-policies.mdx +357 -0
  391. package/.next/standalone/docs/zh/dashboard.mdx +142 -0
  392. package/.next/standalone/docs/zh/examples.mdx +253 -0
  393. package/.next/standalone/docs/zh/for-agents.mdx +38 -0
  394. package/.next/standalone/docs/zh/getting-started.mdx +134 -0
  395. package/.next/standalone/docs/zh/introduction.mdx +57 -0
  396. package/.next/standalone/docs/zh/package-aliases.mdx +82 -0
  397. package/.next/standalone/docs/zh/testing.mdx +260 -0
  398. package/.next/standalone/examples/convention-policies/security-policies.mjs +40 -0
  399. package/.next/standalone/examples/convention-policies/workflow-policies.mjs +41 -0
  400. package/.next/standalone/next.config.ts +5 -3
  401. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  402. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  403. package/.next/standalone/node_modules/next/dist/compiled/jsonwebtoken/index.js +2 -2
  404. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +1 -1
  405. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +1 -1
  406. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
  407. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  408. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  409. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +7 -2
  410. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  411. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  412. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  413. package/.next/standalone/node_modules/next/dist/server/render.js +20 -19
  414. package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  415. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  416. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  417. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  418. package/.next/standalone/node_modules/next/package.json +15 -15
  419. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  420. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  421. package/.next/standalone/node_modules/react/package.json +1 -1
  422. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  423. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  424. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  425. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  426. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  427. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  428. package/.next/standalone/node_modules/react-dom/package.json +2 -2
  429. package/.next/standalone/package.json +13 -10
  430. package/.next/standalone/scripts/translate-docs/cache.ts +62 -0
  431. package/.next/standalone/scripts/translate-docs/cli.ts +357 -0
  432. package/.next/standalone/scripts/translate-docs/config.ts +248 -0
  433. package/.next/standalone/scripts/translate-docs/mdx-translator.ts +153 -0
  434. package/.next/standalone/scripts/translate-docs/mintlify-nav.ts +107 -0
  435. package/.next/standalone/scripts/translate-docs/readme-translator.ts +154 -0
  436. package/.next/standalone/scripts/translate-docs/translator.ts +68 -0
  437. package/.next/standalone/scripts/translate-docs/types.ts +43 -0
  438. package/.next/standalone/server.js +1 -1
  439. package/.next/standalone/skills-lock.json +10 -0
  440. package/.next/standalone/src/hooks/builtin-policies.ts +401 -25
  441. package/.next/standalone/src/hooks/custom-hooks-loader.ts +165 -21
  442. package/.next/standalone/src/hooks/handler.ts +33 -6
  443. package/.next/standalone/src/hooks/hook-activity-store.ts +6 -1
  444. package/.next/standalone/src/hooks/hooks-config.ts +47 -2
  445. package/.next/standalone/src/hooks/llm-client.ts +2 -2
  446. package/.next/standalone/src/hooks/loader-utils.ts +4 -4
  447. package/.next/standalone/src/hooks/manager.ts +67 -16
  448. package/.next/standalone/src/hooks/policy-evaluator.ts +58 -19
  449. package/.next/standalone/src/hooks/policy-helpers.ts +2 -2
  450. package/.next/standalone/vitest.config.e2e.mts +3 -0
  451. package/.next/standalone/vitest.config.mts +3 -0
  452. package/README.md +95 -49
  453. package/bin/failproofai.mjs +5 -0
  454. package/dist/cli.mjs +535 -90
  455. package/dist/index.js +2 -2
  456. package/package.json +13 -10
  457. package/scripts/translate-docs/cache.ts +62 -0
  458. package/scripts/translate-docs/cli.ts +357 -0
  459. package/scripts/translate-docs/config.ts +248 -0
  460. package/scripts/translate-docs/mdx-translator.ts +153 -0
  461. package/scripts/translate-docs/mintlify-nav.ts +107 -0
  462. package/scripts/translate-docs/readme-translator.ts +154 -0
  463. package/scripts/translate-docs/translator.ts +68 -0
  464. package/scripts/translate-docs/types.ts +43 -0
  465. package/src/hooks/builtin-policies.ts +401 -25
  466. package/src/hooks/custom-hooks-loader.ts +165 -21
  467. package/src/hooks/handler.ts +33 -6
  468. package/src/hooks/hook-activity-store.ts +6 -1
  469. package/src/hooks/hooks-config.ts +47 -2
  470. package/src/hooks/llm-client.ts +2 -2
  471. package/src/hooks/loader-utils.ts +4 -4
  472. package/src/hooks/manager.ts +67 -16
  473. package/src/hooks/policy-evaluator.ts +58 -19
  474. package/src/hooks/policy-helpers.ts +2 -2
  475. package/.next/standalone/.next/server/chunks/[root-of-the-server]__02nt~6d._.js +0 -3
  476. package/.next/standalone/.next/static/chunks/0c_ljlxa._4lc.js +0 -6
  477. package/.next/standalone/.next/static/chunks/15jpradyu_531.css +0 -1
  478. package/.next/standalone/docs/cli-reference.md +0 -175
  479. package/.next/standalone/docs/custom-hooks.md +0 -261
  480. package/.next/standalone/docs/getting-started.md +0 -128
  481. package/.next/standalone/docs/introduction.md +0 -47
  482. /package/.next/standalone/.next/static/{WS-OQSqL1Lp1w_obXfjvl → WRbDp8A_ORPof197CezOZ}/_buildManifest.js +0 -0
  483. /package/.next/standalone/.next/static/{WS-OQSqL1Lp1w_obXfjvl → WRbDp8A_ORPof197CezOZ}/_clientMiddlewareManifest.js +0 -0
  484. /package/.next/standalone/.next/static/{WS-OQSqL1Lp1w_obXfjvl → WRbDp8A_ORPof197CezOZ}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -149,11 +149,19 @@ function readMergedHooksConfig(cwd) {
149
149
  ...llm !== undefined ? { llm } : {}
150
150
  };
151
151
  }
152
- function getConfigPath() {
153
- return resolve(homedir2(), ".failproofai", "policies-config.json");
152
+ function getConfigPathForScope(scope, cwd) {
153
+ const base = cwd ? resolve(cwd) : process.cwd();
154
+ switch (scope) {
155
+ case "user":
156
+ return resolve(homedir2(), ".failproofai", "policies-config.json");
157
+ case "project":
158
+ return resolve(base, ".failproofai", "policies-config.json");
159
+ case "local":
160
+ return resolve(base, ".failproofai", "policies-config.local.json");
161
+ }
154
162
  }
155
- function readHooksConfig() {
156
- const configPath = getConfigPath();
163
+ function readScopedHooksConfig(scope, cwd) {
164
+ const configPath = getConfigPathForScope(scope, cwd);
157
165
  if (!existsSync2(configPath)) {
158
166
  return { enabledPolicies: [] };
159
167
  }
@@ -165,8 +173,8 @@ function readHooksConfig() {
165
173
  return { enabledPolicies: [] };
166
174
  }
167
175
  }
168
- function writeHooksConfig(config) {
169
- const configPath = getConfigPath();
176
+ function writeScopedHooksConfig(config, scope, cwd) {
177
+ const configPath = getConfigPathForScope(scope, cwd);
170
178
  const dir = dirname(configPath);
171
179
  if (!existsSync2(dir)) {
172
180
  mkdirSync2(dir, { recursive: true });
@@ -179,8 +187,8 @@ var init_hooks_config = __esm(() => {
179
187
  });
180
188
 
181
189
  // src/hooks/policy-helpers.ts
182
- function allow() {
183
- return { decision: "allow" };
190
+ function allow(reason) {
191
+ return reason ? { decision: "allow", reason } : { decision: "allow" };
184
192
  }
185
193
  function deny(reason) {
186
194
  return { decision: "deny", reason };
@@ -248,7 +256,7 @@ var REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__", INDEX_CACHE_KEY = "__FAILP
248
256
  // src/hooks/builtin-policies.ts
249
257
  import { resolve as resolve2, join as join2 } from "node:path";
250
258
  import { readFile, writeFile } from "node:fs/promises";
251
- import { execSync } from "node:child_process";
259
+ import { execSync, execFileSync } from "node:child_process";
252
260
  import { homedir as homedir3 } from "node:os";
253
261
  function isClaudeInternalPath(resolved2) {
254
262
  const claudeDir = join2(homedir3(), ".claude");
@@ -266,6 +274,53 @@ function getFilePath(ctx) {
266
274
  function parseArgvTokens(cmd) {
267
275
  return cmd.trim().split(/\s+/).map((t) => t.replace(/^['"]|['"]$/g, ""));
268
276
  }
277
+ function getCurrentBranch(cwd) {
278
+ try {
279
+ let branch = gitBranchCache.get(cwd);
280
+ if (branch === undefined) {
281
+ branch = execSync("git rev-parse --abbrev-ref HEAD", {
282
+ cwd,
283
+ encoding: "utf8",
284
+ timeout: 3000
285
+ }).trim();
286
+ gitBranchCache.set(cwd, branch);
287
+ }
288
+ return branch || null;
289
+ } catch {
290
+ return null;
291
+ }
292
+ }
293
+ function getHeadSha(cwd) {
294
+ try {
295
+ const sha = execSync("git rev-parse HEAD", {
296
+ cwd,
297
+ encoding: "utf8",
298
+ timeout: 3000
299
+ }).trim();
300
+ return sha || null;
301
+ } catch {
302
+ return null;
303
+ }
304
+ }
305
+ function getThirdPartyCheckRuns(cwd, sha) {
306
+ try {
307
+ const json = execFileSync("gh", [
308
+ "api",
309
+ `repos/{owner}/{repo}/commits/${sha}/check-runs`,
310
+ "--jq",
311
+ '.check_runs | map(select(.app.slug != "github-actions")) | map({name: .name, status: .status, conclusion: (.conclusion // "")})'
312
+ ], {
313
+ cwd,
314
+ encoding: "utf8",
315
+ timeout: 15000
316
+ }).trim();
317
+ if (!json || json === "[]")
318
+ return [];
319
+ return JSON.parse(json);
320
+ } catch {
321
+ return [];
322
+ }
323
+ }
269
324
  function matchesAllowedPattern(cmd, pattern) {
270
325
  const cmdTokens = parseArgvTokens(cmd);
271
326
  const patTokens = parseArgvTokens(pattern);
@@ -480,7 +535,8 @@ function rmTargetIsAllowed(cmd, allowPaths) {
480
535
  if (rmIdx < 0)
481
536
  continue;
482
537
  const flagTokens = tokens.slice(rmIdx + 1).filter((t) => /^-[^-]/.test(t));
483
- if (!/r/i.test(flagTokens.join("")))
538
+ const longFlagsInSeg = tokens.slice(rmIdx + 1).filter((t) => /^--/.test(t));
539
+ if (!/r/i.test(flagTokens.join("")) && !longFlagsInSeg.some((f) => /^--recursive$/i.test(f)))
484
540
  continue;
485
541
  const pathArgs = tokens.slice(rmIdx + 1).filter((t) => !t.startsWith("-"));
486
542
  for (const target of pathArgs) {
@@ -505,7 +561,10 @@ function blockRmRf(ctx) {
505
561
  if (ctx.toolName !== "Bash")
506
562
  return allow();
507
563
  const cmd = getCommand(ctx);
508
- const hasDestructivePath = /(?:\/\s*$|\/\*|~)/.test(cmd);
564
+ const hasDestructivePath = parseArgvTokens(cmd).some((token) => {
565
+ const normalized = token.replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
566
+ return normalized === "/" || normalized === "~" || /^\/[A-Za-z_][\w.-]*$/.test(normalized);
567
+ });
509
568
  if (hasDestructivePath && (/rm\s+-[^\s]*r[^\s]*f[^\s]*/.test(cmd) || /rm\s+-[^\s]*f[^\s]*r[^\s]*/.test(cmd))) {
510
569
  const allowPaths = ctx.params?.allowPaths ?? [];
511
570
  if (rmTargetIsAllowed(cmd, allowPaths))
@@ -515,7 +574,10 @@ function blockRmRf(ctx) {
515
574
  if (hasDestructivePath && /\brm\b/.test(cmd)) {
516
575
  const tokens = parseArgvTokens(cmd);
517
576
  const shortFlags = tokens.filter((t) => /^-[^-]/.test(t)).join("");
518
- if (/r/i.test(shortFlags) && /f/.test(shortFlags)) {
577
+ const longFlags = tokens.filter((t) => /^--/.test(t));
578
+ const hasRecursive = /r/i.test(shortFlags) || longFlags.some((f) => /^--recursive$/i.test(f));
579
+ const hasForce = /f/.test(shortFlags) || longFlags.some((f) => /^--force$/i.test(f));
580
+ if (hasRecursive && hasForce) {
519
581
  const allowPaths = ctx.params?.allowPaths ?? [];
520
582
  if (rmTargetIsAllowed(cmd, allowPaths))
521
583
  return allow();
@@ -653,22 +715,12 @@ function blockWorkOnMain(ctx) {
653
715
  const cwd = ctx.session?.cwd;
654
716
  if (!cwd)
655
717
  return allow();
656
- try {
657
- let branch = gitBranchCache.get(cwd);
658
- if (branch === undefined) {
659
- branch = execSync("git rev-parse --abbrev-ref HEAD", {
660
- cwd,
661
- encoding: "utf8",
662
- timeout: 3000
663
- }).trim();
664
- gitBranchCache.set(cwd, branch);
665
- }
666
- const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
667
- if (protectedBranches.includes(branch)) {
668
- return deny(`Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`);
669
- }
670
- } catch {
718
+ const branch = getCurrentBranch(cwd);
719
+ if (!branch)
671
720
  return allow();
721
+ const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
722
+ if (protectedBranches.includes(branch)) {
723
+ return deny(`Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`);
672
724
  }
673
725
  return allow();
674
726
  }
@@ -767,6 +819,170 @@ function warnBackgroundProcess(ctx) {
767
819
  }
768
820
  return allow();
769
821
  }
822
+ function requireCommitBeforeStop(ctx) {
823
+ const cwd = ctx.session?.cwd;
824
+ if (!cwd)
825
+ return allow("No working directory available, skipping commit check.");
826
+ try {
827
+ const status = execSync("git status --porcelain", {
828
+ cwd,
829
+ encoding: "utf8",
830
+ timeout: 5000
831
+ }).trim();
832
+ if (status.length > 0) {
833
+ return deny("You have uncommitted changes in the working directory. Commit all changes before stopping.");
834
+ }
835
+ return allow("All changes are committed.");
836
+ } catch {
837
+ return allow("Not a git repository, skipping commit check.");
838
+ }
839
+ }
840
+ function requirePushBeforeStop(ctx) {
841
+ const cwd = ctx.session?.cwd;
842
+ if (!cwd)
843
+ return allow("No working directory available, skipping push check.");
844
+ try {
845
+ const remotes = execSync("git remote", {
846
+ cwd,
847
+ encoding: "utf8",
848
+ timeout: 3000
849
+ }).trim();
850
+ if (!remotes)
851
+ return allow("No git remote configured, skipping push check.");
852
+ const remote = ctx.params?.remote ?? "origin";
853
+ const branch = getCurrentBranch(cwd);
854
+ if (!branch || branch === "HEAD")
855
+ return allow("Detached HEAD, skipping push check.");
856
+ const baseBranch = ctx.params?.baseBranch ?? "main";
857
+ if (branch === baseBranch) {
858
+ return allow(`On base branch "${baseBranch}", skipping push check.`);
859
+ }
860
+ try {
861
+ const ahead = execFileSync("git", ["log", `${remote}/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
862
+ if (!ahead) {
863
+ return allow(`No commits ahead of ${remote}/${baseBranch}, skipping push check.`);
864
+ }
865
+ const diff = execFileSync("git", ["diff", "--stat", `${remote}/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
866
+ if (!diff) {
867
+ return allow(`No file changes compared to ${remote}/${baseBranch}, skipping push check.`);
868
+ }
869
+ } catch {}
870
+ let hasTracking = false;
871
+ try {
872
+ execFileSync("git", ["rev-parse", "--verify", `${remote}/${branch}`], {
873
+ cwd,
874
+ encoding: "utf8",
875
+ timeout: 3000
876
+ });
877
+ hasTracking = true;
878
+ } catch {}
879
+ if (!hasTracking) {
880
+ return deny(`Branch "${branch}" has not been pushed to remote "${remote}". ` + `Push your branch with: git push -u ${remote} ${branch}`);
881
+ }
882
+ const unpushed = execFileSync("git", ["log", `${remote}/${branch}..HEAD`, "--oneline"], {
883
+ cwd,
884
+ encoding: "utf8",
885
+ timeout: 5000
886
+ }).trim();
887
+ if (unpushed.length > 0) {
888
+ const commitCount = unpushed.split(`
889
+ `).length;
890
+ return deny(`You have ${commitCount} unpushed commit${commitCount > 1 ? "s" : ""} on branch "${branch}". ` + `Push your changes with: git push`);
891
+ }
892
+ return allow(`All commits pushed to "${remote}".`);
893
+ } catch {
894
+ return allow("Could not check push status, skipping.");
895
+ }
896
+ }
897
+ function requirePrBeforeStop(ctx) {
898
+ const cwd = ctx.session?.cwd;
899
+ if (!cwd)
900
+ return allow("No working directory available, skipping PR check.");
901
+ try {
902
+ try {
903
+ execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
904
+ } catch {
905
+ return allow("GitHub CLI (gh) not installed, skipping PR check.");
906
+ }
907
+ const branch = getCurrentBranch(cwd);
908
+ if (!branch || branch === "HEAD")
909
+ return allow("Detached HEAD, skipping PR check.");
910
+ const baseBranch = ctx.params?.baseBranch ?? "main";
911
+ if (branch === baseBranch) {
912
+ return allow(`On base branch "${baseBranch}", skipping PR check.`);
913
+ }
914
+ try {
915
+ const ahead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
916
+ if (!ahead) {
917
+ return allow(`No commits ahead of origin/${baseBranch}, skipping PR check.`);
918
+ }
919
+ const diff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
920
+ if (!diff) {
921
+ return allow(`No file changes compared to origin/${baseBranch}, skipping PR check.`);
922
+ }
923
+ } catch {}
924
+ let prJson;
925
+ try {
926
+ prJson = execSync("gh pr view --json number,url,state", {
927
+ cwd,
928
+ encoding: "utf8",
929
+ timeout: 15000
930
+ }).trim();
931
+ } catch {
932
+ return deny(`No pull request found for branch "${branch}". ` + `Create one with: gh pr create`);
933
+ }
934
+ const pr = JSON.parse(prJson);
935
+ if (pr.state === "OPEN") {
936
+ return allow(`PR #${pr.number} exists: ${pr.url}`);
937
+ }
938
+ return deny(`Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Create a new PR with: gh pr create`);
939
+ } catch {
940
+ return allow("Could not check PR status, skipping.");
941
+ }
942
+ }
943
+ function requireCiGreenBeforeStop(ctx) {
944
+ const cwd = ctx.session?.cwd;
945
+ if (!cwd)
946
+ return allow("No working directory available, skipping CI check.");
947
+ try {
948
+ try {
949
+ execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
950
+ } catch {
951
+ return allow("GitHub CLI (gh) not installed, skipping CI check.");
952
+ }
953
+ const branch = getCurrentBranch(cwd);
954
+ if (!branch || branch === "HEAD")
955
+ return allow("Detached HEAD, skipping CI check.");
956
+ let workflowRuns = [];
957
+ try {
958
+ const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"], { cwd, encoding: "utf8", timeout: 15000 }).trim();
959
+ if (runsJson && runsJson !== "[]") {
960
+ workflowRuns = JSON.parse(runsJson);
961
+ }
962
+ } catch {}
963
+ let thirdPartyChecks = [];
964
+ const sha = getHeadSha(cwd);
965
+ if (sha) {
966
+ thirdPartyChecks = getThirdPartyCheckRuns(cwd, sha);
967
+ }
968
+ const allChecks = [...workflowRuns, ...thirdPartyChecks];
969
+ if (allChecks.length === 0)
970
+ return allow(`No CI runs found for branch "${branch}".`);
971
+ const failing = allChecks.filter((r) => r.status === "completed" && r.conclusion !== "success" && r.conclusion !== "skipped");
972
+ if (failing.length > 0) {
973
+ const names = failing.map((r) => `"${r.name}"`).join(", ");
974
+ return deny(`CI checks are failing on branch "${branch}": ${names}. Fix the failing checks before stopping.`);
975
+ }
976
+ const pending = allChecks.filter((r) => r.status === "in_progress" || r.status === "queued" || r.status === "waiting");
977
+ if (pending.length > 0) {
978
+ const names = pending.map((r) => `"${r.name}"`).join(", ");
979
+ return deny(`CI checks are still running on branch "${branch}": ${names}. Wait for all checks to complete and verify they pass.`);
980
+ }
981
+ return allow(`All CI checks passed on branch "${branch}".`);
982
+ } catch {
983
+ return allow("Could not check CI status, skipping.");
984
+ }
985
+ }
770
986
  function registerBuiltinPolicies(enabledNames) {
771
987
  const enabledSet = new Set(enabledNames);
772
988
  for (const policy of BUILTIN_POLICIES) {
@@ -802,7 +1018,7 @@ var init_builtin_policies = __esm(() => {
802
1018
  SCHEMA_ALTER_RE = /\bALTER\s+TABLE\b[\s\S]*\b(?:DROP\s+COLUMN|ADD\s+COLUMN|RENAME\s+(?:COLUMN|TO)|MODIFY\s+COLUMN)\b/i;
803
1019
  PUBLISH_CMD_RE = /(?:npm\s+publish|bun\s+publish|pnpm\s+publish|yarn\s+npm\s+publish|twine\s+upload|poetry\s+publish|cargo\s+publish|gem\s+push)\b/;
804
1020
  ENV_PRINTENV_RE = /(?:^|\s|;|&&|\|\|)(?:env|printenv)(?:\s|$|;|&&|\|)/;
805
- ECHO_ENV_RE = /echo\s+.*\$[A-Za-z_]/;
1021
+ ECHO_ENV_RE = /echo\s+.*\$\{?[A-Za-z_]/;
806
1022
  EXPORT_RE = /(?:^|\s|;|&&|\|\|)export\s+\w+/;
807
1023
  PS_ENV_VAR_RE = /\$env:[A-Za-z_]/i;
808
1024
  PS_CHILDITEM_ENV_RE = /(?:Get-ChildItem|dir|gci|ls)\s+Env:/i;
@@ -813,7 +1029,7 @@ var init_builtin_policies = __esm(() => {
813
1029
  SUDO_RE = /(?:^|;|&&|\|\|)\s*sudo\s/;
814
1030
  PS_ELEVATION_RE = /Start-Process\s+.*-Verb\s+RunAs/i;
815
1031
  RUNAS_RE = /(?:^|;|&&|\|\|)\s*runas\s/i;
816
- CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh)/;
1032
+ CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh|dash|ksh|csh|tcsh|fish|ash)\b/;
817
1033
  PS_WEB_PIPE_RE = /(?:Invoke-WebRequest|iwr|Invoke-RestMethod|irm)\s+.*\|\s*(?:Invoke-Expression|iex)/i;
818
1034
  FORCE_PUSH_RE = /(?:--force|-f\b)/;
819
1035
  SECRET_FILE_RE = /\.(?:pem|key)$/;
@@ -1102,11 +1318,71 @@ var init_builtin_policies = __esm(() => {
1102
1318
  match: { events: ["PreToolUse"] },
1103
1319
  defaultEnabled: false,
1104
1320
  category: "AI Behavior"
1321
+ },
1322
+ {
1323
+ name: "require-commit-before-stop",
1324
+ description: "Require all changes to be committed before Claude stops",
1325
+ fn: requireCommitBeforeStop,
1326
+ match: { events: ["Stop"] },
1327
+ defaultEnabled: false,
1328
+ category: "Workflow"
1329
+ },
1330
+ {
1331
+ name: "require-push-before-stop",
1332
+ description: "Require all commits to be pushed to remote before Claude stops",
1333
+ fn: requirePushBeforeStop,
1334
+ match: { events: ["Stop"] },
1335
+ defaultEnabled: false,
1336
+ category: "Workflow",
1337
+ params: {
1338
+ remote: {
1339
+ type: "string",
1340
+ description: "Remote name to push to (default: origin)",
1341
+ default: "origin"
1342
+ },
1343
+ baseBranch: {
1344
+ type: "string",
1345
+ description: "Base branch to compare against (default: main)",
1346
+ default: "main"
1347
+ }
1348
+ }
1349
+ },
1350
+ {
1351
+ name: "require-pr-before-stop",
1352
+ description: "Require a pull request to exist for the current branch before Claude stops",
1353
+ fn: requirePrBeforeStop,
1354
+ match: { events: ["Stop"] },
1355
+ defaultEnabled: false,
1356
+ category: "Workflow",
1357
+ params: {
1358
+ baseBranch: {
1359
+ type: "string",
1360
+ description: "Base branch to compare against (default: main)",
1361
+ default: "main"
1362
+ }
1363
+ }
1364
+ },
1365
+ {
1366
+ name: "require-ci-green-before-stop",
1367
+ description: "Require CI checks to pass on the current branch before Claude stops",
1368
+ fn: requireCiGreenBeforeStop,
1369
+ match: { events: ["Stop"] },
1370
+ defaultEnabled: false,
1371
+ category: "Workflow"
1105
1372
  }
1106
1373
  ];
1107
1374
  });
1108
1375
 
1109
1376
  // src/hooks/policy-evaluator.ts
1377
+ function appendHint(baseReason, hint) {
1378
+ const base = baseReason.trim();
1379
+ const normalizedHint = typeof hint === "string" ? hint.trim() : "";
1380
+ if (!normalizedHint)
1381
+ return base;
1382
+ if (!base)
1383
+ return normalizedHint;
1384
+ return `${base}. ${normalizedHint}`;
1385
+ }
1110
1386
  async function evaluatePolicies(eventType, payload, session, config) {
1111
1387
  const toolName = payload.tool_name;
1112
1388
  const toolInput = payload.tool_input;
@@ -1122,8 +1398,8 @@ async function evaluatePolicies(eventType, payload, session, config) {
1122
1398
  toolInput,
1123
1399
  session
1124
1400
  };
1125
- let instructPolicyName = null;
1126
- let instructReason = null;
1401
+ const instructEntries = [];
1402
+ const allowEntries = [];
1127
1403
  for (const policy of policies) {
1128
1404
  const schema = POLICY_PARAMS_MAP.get(policy.name);
1129
1405
  let ctx;
@@ -1145,7 +1421,7 @@ async function evaluatePolicies(eventType, payload, session, config) {
1145
1421
  continue;
1146
1422
  }
1147
1423
  if (result.decision === "deny") {
1148
- const reason = result.reason ?? `Blocked by policy: ${policy.name}`;
1424
+ const reason = appendHint(result.reason ?? `Blocked by policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1149
1425
  hookLogInfo(`deny by "${policy.name}": ${reason}`);
1150
1426
  const displayTool = ctx.toolName ?? "unknown tool";
1151
1427
  if (eventType === "PreToolUse") {
@@ -1184,44 +1460,63 @@ async function evaluatePolicies(eventType, payload, session, config) {
1184
1460
  return {
1185
1461
  exitCode: 2,
1186
1462
  stdout: "",
1187
- stderr: "",
1463
+ stderr: reason,
1188
1464
  policyName: policy.name,
1189
1465
  reason,
1190
1466
  decision: "deny"
1191
1467
  };
1192
1468
  }
1193
- if (result.decision === "instruct" && !instructPolicyName) {
1194
- instructPolicyName = policy.name;
1195
- instructReason = result.reason ?? `Instruction from policy: ${policy.name}`;
1196
- hookLogInfo(`instruct by "${policy.name}": ${instructReason}`);
1469
+ if (result.decision === "instruct") {
1470
+ const reason = appendHint(result.reason ?? `Instruction from policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1471
+ instructEntries.push({ policyName: policy.name, reason });
1472
+ hookLogInfo(`instruct by "${policy.name}": ${reason}`);
1473
+ }
1474
+ if (result.decision === "allow" && result.reason) {
1475
+ allowEntries.push({ policyName: policy.name, reason: result.reason });
1197
1476
  }
1198
1477
  }
1199
- if (instructPolicyName && instructReason) {
1478
+ if (instructEntries.length > 0) {
1479
+ const combined = instructEntries.map((e) => e.reason).join(`
1480
+ `);
1481
+ const policyNames = instructEntries.map((e) => e.policyName);
1200
1482
  if (eventType === "Stop") {
1201
1483
  return {
1202
1484
  exitCode: 2,
1203
1485
  stdout: "",
1204
- stderr: instructReason,
1205
- policyName: instructPolicyName,
1206
- reason: instructReason,
1486
+ stderr: combined,
1487
+ policyName: policyNames[0],
1488
+ policyNames,
1489
+ reason: combined,
1207
1490
  decision: "instruct"
1208
1491
  };
1209
1492
  }
1210
1493
  const response = {
1211
1494
  hookSpecificOutput: {
1212
1495
  hookEventName: eventType,
1213
- additionalContext: `Instruction from failproofai: ${instructReason}`
1496
+ additionalContext: `Instruction from failproofai: ${combined}`
1214
1497
  }
1215
1498
  };
1216
1499
  return {
1217
1500
  exitCode: 0,
1218
1501
  stdout: JSON.stringify(response),
1219
1502
  stderr: "",
1220
- policyName: instructPolicyName,
1221
- reason: instructReason,
1503
+ policyName: policyNames[0],
1504
+ policyNames,
1505
+ reason: combined,
1222
1506
  decision: "instruct"
1223
1507
  };
1224
1508
  }
1509
+ if (allowEntries.length > 0) {
1510
+ const combined = allowEntries.map((e) => e.reason).join(`
1511
+ `);
1512
+ const policyNames = allowEntries.map((e) => e.policyName);
1513
+ const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit";
1514
+ const response = supportsHookSpecificOutput ? { hookSpecificOutput: { hookEventName: eventType, additionalContext: `Note from failproofai: ${combined}` } } : { reason: combined };
1515
+ const stderrMsg = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
1516
+ `);
1517
+ return { exitCode: 0, stdout: JSON.stringify(response), stderr: stderrMsg + `
1518
+ `, policyName: policyNames[0], policyNames, reason: combined, decision: "allow" };
1519
+ }
1225
1520
  return { exitCode: 0, stdout: "", stderr: "", policyName: null, reason: null, decision: "allow" };
1226
1521
  }
1227
1522
  var POLICY_PARAMS_MAP;
@@ -1291,10 +1586,9 @@ async function createEsmShim(distIndex, distUrl) {
1291
1586
  const shimPath = distIndex + ".__failproofai_esm_shim__.mjs";
1292
1587
  const shimCode = [
1293
1588
  `import _cjs from '${distUrl}';`,
1294
- `export const createApp = _cjs.createApp;`,
1295
- `export const getQueueCondition = _cjs.getQueueCondition;`,
1296
- `export const clearQueueCondition = _cjs.clearQueueCondition;`,
1297
1589
  `export const customPolicies = _cjs.customPolicies;`,
1590
+ `export const getCustomHooks = _cjs.getCustomHooks;`,
1591
+ `export const clearCustomHooks = _cjs.clearCustomHooks;`,
1298
1592
  `export const allow = _cjs.allow;`,
1299
1593
  `export const deny = _cjs.deny;`,
1300
1594
  `export const instruct = _cjs.instruct;`,
@@ -1374,20 +1668,21 @@ var init_loader_utils = __esm(() => {
1374
1668
  });
1375
1669
 
1376
1670
  // src/hooks/custom-hooks-loader.ts
1377
- import { resolve as resolve4, isAbsolute } from "node:path";
1378
- import { existsSync as existsSync3 } from "node:fs";
1671
+ import { resolve as resolve4, isAbsolute, basename } from "node:path";
1672
+ import { existsSync as existsSync3, readdirSync } from "node:fs";
1379
1673
  import { pathToFileURL as pathToFileURL2 } from "node:url";
1380
- async function loadCustomHooks(customPoliciesPath, opts) {
1381
- if (!customPoliciesPath)
1674
+ import { homedir as homedir4 } from "node:os";
1675
+ function discoverPolicyFiles(dir) {
1676
+ if (!existsSync3(dir))
1382
1677
  return [];
1383
- const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(process.cwd(), customPoliciesPath);
1384
- if (!existsSync3(absPath)) {
1385
- if (opts?.strict)
1386
- throw new Error(`Custom hooks file not found: ${absPath}`);
1387
- hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1678
+ try {
1679
+ const entries = readdirSync(dir, { withFileTypes: true });
1680
+ return entries.filter((e) => e.isFile() && CONVENTION_FILE_RE.test(e.name)).sort((a, b) => a.name.localeCompare(b.name)).map((e) => resolve4(dir, e.name));
1681
+ } catch {
1388
1682
  return [];
1389
1683
  }
1390
- clearCustomHooks();
1684
+ }
1685
+ async function loadSingleFile(absPath, opts) {
1391
1686
  const g = globalThis;
1392
1687
  g[LOADING_KEY] = true;
1393
1688
  let tmpFiles = [];
@@ -1403,18 +1698,93 @@ async function loadCustomHooks(customPoliciesPath, opts) {
1403
1698
  if (opts?.strict)
1404
1699
  throw new Error(`Failed to load custom hooks from ${absPath}: ${msg}`);
1405
1700
  hookLogError(`failed to load custom hooks from ${absPath}: ${msg}`);
1406
- return [];
1407
1701
  } finally {
1408
1702
  g[LOADING_KEY] = false;
1409
1703
  await cleanupTmpFiles(tmpFiles);
1410
1704
  }
1705
+ }
1706
+ async function loadCustomHooks(customPoliciesPath, opts) {
1707
+ if (!customPoliciesPath)
1708
+ return [];
1709
+ const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1710
+ if (!existsSync3(absPath)) {
1711
+ if (opts?.strict)
1712
+ throw new Error(`Custom hooks file not found: ${absPath}`);
1713
+ hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1714
+ return [];
1715
+ }
1716
+ clearCustomHooks();
1717
+ await loadSingleFile(absPath, opts);
1411
1718
  return getCustomHooks();
1412
1719
  }
1413
- var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__";
1720
+ async function loadAllCustomHooks(customPoliciesPath, opts) {
1721
+ clearCustomHooks();
1722
+ const conventionSources = [];
1723
+ if (customPoliciesPath) {
1724
+ const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1725
+ if (existsSync3(absPath)) {
1726
+ await loadSingleFile(absPath);
1727
+ } else {
1728
+ hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1729
+ }
1730
+ }
1731
+ const hooksBeforeConvention = getCustomHooks().length;
1732
+ const projectDir = resolve4(opts?.sessionCwd ?? process.cwd(), ".failproofai", "policies");
1733
+ const projectFiles = discoverPolicyFiles(projectDir);
1734
+ for (const file of projectFiles) {
1735
+ const hooksBefore = getCustomHooks().length;
1736
+ await loadSingleFile(file);
1737
+ const newHooks = getCustomHooks().slice(hooksBefore);
1738
+ if (newHooks.length > 0) {
1739
+ conventionSources.push({
1740
+ scope: "project",
1741
+ file: basename(file),
1742
+ hookNames: newHooks.map((h) => h.name)
1743
+ });
1744
+ }
1745
+ }
1746
+ const userDir = resolve4(homedir4(), ".failproofai", "policies");
1747
+ const userFiles = discoverPolicyFiles(userDir);
1748
+ for (const file of userFiles) {
1749
+ const hooksBefore = getCustomHooks().length;
1750
+ await loadSingleFile(file);
1751
+ const newHooks = getCustomHooks().slice(hooksBefore);
1752
+ if (newHooks.length > 0) {
1753
+ conventionSources.push({
1754
+ scope: "user",
1755
+ file: basename(file),
1756
+ hookNames: newHooks.map((h) => h.name)
1757
+ });
1758
+ }
1759
+ }
1760
+ const allHooks = getCustomHooks();
1761
+ const conventionCount = allHooks.length - hooksBeforeConvention;
1762
+ if (projectFiles.length > 0 || userFiles.length > 0) {
1763
+ hookLogInfo(`convention policies: ${projectFiles.length} project file(s), ${userFiles.length} user file(s), ${conventionCount} hook(s)`);
1764
+ }
1765
+ const hookNameToScope = new Map;
1766
+ for (const source of conventionSources) {
1767
+ for (const name of source.hookNames) {
1768
+ hookNameToScope.set(name, source.scope);
1769
+ }
1770
+ }
1771
+ const conventionHookRefs = new Set;
1772
+ for (const hook of allHooks.slice(hooksBeforeConvention)) {
1773
+ conventionHookRefs.add(hook);
1774
+ }
1775
+ for (const hook of allHooks) {
1776
+ if (conventionHookRefs.has(hook)) {
1777
+ hook.__conventionScope = hookNameToScope.get(hook.name) ?? "project";
1778
+ }
1779
+ }
1780
+ return { hooks: allHooks, conventionSources };
1781
+ }
1782
+ var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__", CONVENTION_FILE_RE;
1414
1783
  var init_custom_hooks_loader = __esm(() => {
1415
1784
  init_hook_logger();
1416
1785
  init_custom_hooks_registry();
1417
1786
  init_loader_utils();
1787
+ CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
1418
1788
  });
1419
1789
 
1420
1790
  // src/hooks/hook-activity-store.ts
@@ -1423,14 +1793,14 @@ import {
1423
1793
  writeFileSync as writeFileSync2,
1424
1794
  appendFileSync as appendFileSync2,
1425
1795
  renameSync as renameSync2,
1426
- readdirSync,
1796
+ readdirSync as readdirSync2,
1427
1797
  mkdirSync as mkdirSync3,
1428
1798
  existsSync as existsSync4,
1429
1799
  statSync as statSync2,
1430
1800
  unlinkSync
1431
1801
  } from "node:fs";
1432
1802
  import { join as join3 } from "node:path";
1433
- import { homedir as homedir4 } from "node:os";
1803
+ import { homedir as homedir5 } from "node:os";
1434
1804
  function ensureDir() {
1435
1805
  if (!existsSync4(storeDir)) {
1436
1806
  mkdirSync3(storeDir, { recursive: true });
@@ -1516,7 +1886,11 @@ function updateStats(entry) {
1516
1886
  s.totalEvents += 1;
1517
1887
  if (entry.decision === "deny")
1518
1888
  s.denyCount += 1;
1519
- if (entry.policyName) {
1889
+ if (entry.policyNames && entry.policyNames.length > 0) {
1890
+ for (const name of entry.policyNames) {
1891
+ s.policyMap[name] = (s.policyMap[name] ?? 0) + 1;
1892
+ }
1893
+ } else if (entry.policyName) {
1520
1894
  s.policyMap[entry.policyName] = (s.policyMap[entry.policyName] ?? 0) + 1;
1521
1895
  }
1522
1896
  const tmpPath = join3(storeDir, `stats.json.${process.pid}.tmp`);
@@ -1531,12 +1905,12 @@ function updateStats(entry) {
1531
1905
  }
1532
1906
  var PAGE_SIZE = 25, DEFAULT_STORE_DIR, CURRENT_FILE = "current.jsonl", COUNT_FILE = "current.count", STATS_FILE = "stats.json", LOCK_FILE = "current.lock", LOCK_STALE_MS = 2000, storeDir, rotateSeq = 0;
1533
1907
  var init_hook_activity_store = __esm(() => {
1534
- DEFAULT_STORE_DIR = join3(homedir4(), ".failproofai", "cache", "hook-activity");
1908
+ DEFAULT_STORE_DIR = join3(homedir5(), ".failproofai", "cache", "hook-activity");
1535
1909
  storeDir = DEFAULT_STORE_DIR;
1536
1910
  });
1537
1911
 
1538
1912
  // package.json
1539
- var version2 = "0.0.2";
1913
+ var version2 = "0.0.4-beta.0";
1540
1914
  var init_package = () => {};
1541
1915
 
1542
1916
  // src/posthog-key.ts
@@ -1698,9 +2072,14 @@ async function handleHookEvent(eventType) {
1698
2072
  const config = readMergedHooksConfig(session.cwd);
1699
2073
  clearPolicies();
1700
2074
  registerBuiltinPolicies(config.enabledPolicies);
1701
- const customHooksList = await loadCustomHooks(config.customPoliciesPath);
2075
+ const loadResult = await loadAllCustomHooks(config.customPoliciesPath, { sessionCwd: session.cwd });
2076
+ const customHooksList = loadResult.hooks;
2077
+ const conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
1702
2078
  for (const hook of customHooksList) {
1703
2079
  const hookName = hook.name;
2080
+ const conventionScope = hook.__conventionScope;
2081
+ const isConvention = !!conventionScope;
2082
+ const prefix = isConvention ? `.failproofai-${conventionScope}` : "custom";
1704
2083
  const fn = async (ctx) => {
1705
2084
  try {
1706
2085
  const result2 = await Promise.race([
@@ -1711,16 +2090,18 @@ async function handleHookEvent(eventType) {
1711
2090
  } catch (err) {
1712
2091
  const msg = err instanceof Error ? err.message : String(err);
1713
2092
  const isTimeout = msg === "timeout";
1714
- hookLogWarn(`custom hook "${hookName}" failed: ${msg}`);
2093
+ hookLogWarn(`${prefix} hook "${hookName}" failed: ${msg}`);
1715
2094
  trackHookEvent(getInstanceId(), "custom_hook_error", {
1716
2095
  hook_name: hookName,
1717
2096
  error_type: isTimeout ? "timeout" : "exception",
1718
- event_type: eventType
2097
+ event_type: eventType,
2098
+ is_convention_policy: isConvention,
2099
+ convention_scope: conventionScope ?? null
1719
2100
  });
1720
2101
  return { decision: "allow" };
1721
2102
  }
1722
2103
  };
1723
- registerPolicy(`custom/${hookName}`, hook.description ?? "", fn, hook.match ?? {}, -1);
2104
+ registerPolicy(`${prefix}/${hookName}`, hook.description ?? "", fn, hook.match ?? {}, -1);
1724
2105
  }
1725
2106
  if (customHooksList.length > 0) {
1726
2107
  trackHookEvent(getInstanceId(), "custom_hooks_loaded", {
@@ -1729,7 +2110,16 @@ async function handleHookEvent(eventType) {
1729
2110
  event_types_covered: [...new Set(customHooksList.flatMap((h) => h.match?.events ?? []))]
1730
2111
  });
1731
2112
  }
1732
- hookLogInfo(`event=${eventType} policies=${config.enabledPolicies.length} custom=${customHooksList.length}`);
2113
+ if (loadResult.conventionSources.length > 0) {
2114
+ trackHookEvent(getInstanceId(), "convention_policies_loaded", {
2115
+ event_type: eventType,
2116
+ project_file_count: loadResult.conventionSources.filter((s) => s.scope === "project").length,
2117
+ user_file_count: loadResult.conventionSources.filter((s) => s.scope === "user").length,
2118
+ convention_hook_count: conventionHookNames.size,
2119
+ convention_hook_names: [...conventionHookNames]
2120
+ });
2121
+ }
2122
+ hookLogInfo(`event=${eventType} policies=${config.enabledPolicies.length} custom=${customHooksList.length} convention=${conventionHookNames.size}`);
1733
2123
  const result = await evaluatePolicies(eventType, parsed, session, config);
1734
2124
  const durationMs = Math.round(performance.now() - startTime);
1735
2125
  hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
@@ -1745,6 +2135,7 @@ async function handleHookEvent(eventType) {
1745
2135
  eventType,
1746
2136
  toolName: parsed.tool_name ?? null,
1747
2137
  policyName: result.policyName,
2138
+ policyNames: result.policyNames,
1748
2139
  decision: result.decision,
1749
2140
  reason: result.reason,
1750
2141
  durationMs,
@@ -1760,7 +2151,9 @@ async function handleHookEvent(eventType) {
1760
2151
  if (result.decision === "deny" || result.decision === "instruct") {
1761
2152
  try {
1762
2153
  const isCustomHook = result.policyName?.startsWith("custom/") ?? false;
1763
- const hasCustomParams = !isCustomHook && !!(result.policyName && config.policyParams?.[result.policyName]);
2154
+ const isConventionPolicy = result.policyName?.startsWith(".failproofai-") ?? false;
2155
+ const conventionScope = isConventionPolicy ? result.policyName.match(/^\.failproofai-(project|user)\//)?.[1] ?? null : null;
2156
+ const hasCustomParams = !isCustomHook && !isConventionPolicy && !!(result.policyName && config.policyParams?.[result.policyName]);
1764
2157
  const paramKeysOverridden = hasCustomParams ? Object.keys(config.policyParams[result.policyName]) : [];
1765
2158
  const distinctId = getInstanceId();
1766
2159
  await trackHookEvent(distinctId, "hook_policy_triggered", {
@@ -1769,6 +2162,8 @@ async function handleHookEvent(eventType) {
1769
2162
  policy_name: result.policyName,
1770
2163
  decision: result.decision,
1771
2164
  is_custom_hook: isCustomHook,
2165
+ is_convention_policy: isConventionPolicy,
2166
+ convention_scope: conventionScope,
1772
2167
  has_custom_params: hasCustomParams,
1773
2168
  param_keys_overridden: paramKeysOverridden
1774
2169
  });
@@ -2101,13 +2496,13 @@ __export(exports_manager, {
2101
2496
  });
2102
2497
  import { execSync as execSync3 } from "node:child_process";
2103
2498
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
2104
- import { resolve as resolve5, dirname as dirname3 } from "node:path";
2105
- import { homedir as homedir5, platform, arch, release, hostname } from "node:os";
2499
+ import { resolve as resolve5, dirname as dirname3, basename as basename2 } from "node:path";
2500
+ import { homedir as homedir6, platform, arch, release, hostname } from "node:os";
2106
2501
  function getSettingsPath(scope, cwd) {
2107
2502
  const base = cwd ? resolve5(cwd) : process.cwd();
2108
2503
  switch (scope) {
2109
2504
  case "user":
2110
- return resolve5(homedir5(), ".claude", "settings.json");
2505
+ return resolve5(homedir6(), ".claude", "settings.json");
2111
2506
  case "project":
2112
2507
  return resolve5(base, ".claude", "settings.json");
2113
2508
  case "local":
@@ -2234,7 +2629,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
2234
2629
  }
2235
2630
  }
2236
2631
  const binaryPath = resolveFailproofaiBinary();
2237
- const previousConfig = readHooksConfig();
2632
+ const previousConfig = readScopedHooksConfig(scope, cwd);
2238
2633
  const previousEnabled = new Set(previousConfig.enabledPolicies);
2239
2634
  let selectedPolicies;
2240
2635
  if (policyNames !== undefined) {
@@ -2268,7 +2663,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
2268
2663
  console.log(`
2269
2664
  Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
2270
2665
  }
2271
- writeHooksConfig(configToWrite);
2666
+ writeScopedHooksConfig(configToWrite, scope, cwd);
2272
2667
  console.log(`
2273
2668
  Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`);
2274
2669
  if (removeCustomHooks) {
@@ -2282,7 +2677,7 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
2282
2677
  settings.hooks = {};
2283
2678
  }
2284
2679
  for (const eventType of HOOK_EVENT_TYPES) {
2285
- const command = `"${binaryPath}" --hook ${eventType}`;
2680
+ const command = scope === "project" ? `npx -y failproofai --hook ${eventType}` : `"${binaryPath}" --hook ${eventType}`;
2286
2681
  const hookEntry = {
2287
2682
  type: "command",
2288
2683
  command,
@@ -2327,12 +2722,19 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
2327
2722
  hostname_hash: hashToId(hostname()),
2328
2723
  has_custom_hooks_path: !!configToWrite.customPoliciesPath,
2329
2724
  has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
2330
- param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : []
2725
+ param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
2726
+ command_format: scope === "project" ? "npx" : "absolute"
2331
2727
  });
2332
2728
  } catch {}
2333
2729
  console.log(`Failproof AI hooks installed for all ${HOOK_EVENT_TYPES.length} event types (scope: ${scope}).`);
2334
2730
  console.log(`Settings: ${settingsPath}`);
2335
- console.log(`Binary: ${binaryPath}`);
2731
+ if (scope === "project") {
2732
+ console.log(`Command: npx -y failproofai`);
2733
+ console.log(`
2734
+ This file can be committed to git — no machine-specific paths.`);
2735
+ } else {
2736
+ console.log(`Binary: ${binaryPath}`);
2737
+ }
2336
2738
  const otherScopes = deduplicateScopes(HOOK_SCOPES, cwd).filter((s) => s !== scope);
2337
2739
  const duplicates = otherScopes.filter((s) => hooksInstalledInSettings(s, cwd));
2338
2740
  if (duplicates.length > 0) {
@@ -2345,15 +2747,16 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
2345
2747
  }
2346
2748
  }
2347
2749
  async function removeHooks(policyNames, scope = "user", cwd, opts) {
2750
+ const configScope = scope === "all" ? "user" : scope;
2348
2751
  if (opts?.removeCustomHooks) {
2349
- const config = readHooksConfig();
2752
+ const config = readScopedHooksConfig(configScope, cwd);
2350
2753
  delete config.customPoliciesPath;
2351
- writeHooksConfig(config);
2754
+ writeScopedHooksConfig(config, configScope, cwd);
2352
2755
  console.log("Custom hooks path cleared.");
2353
2756
  }
2354
2757
  if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
2355
2758
  validatePolicyNames(policyNames);
2356
- const config = readHooksConfig();
2759
+ const config = readScopedHooksConfig(configScope, cwd);
2357
2760
  const removeSet = new Set(policyNames);
2358
2761
  const remaining = config.enabledPolicies.filter((p) => !removeSet.has(p));
2359
2762
  const notEnabled = policyNames.filter((p) => !config.enabledPolicies.includes(p));
@@ -2367,7 +2770,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
2367
2770
  enabledPolicies: remaining,
2368
2771
  ...filteredParams && Object.keys(filteredParams).length > 0 ? { policyParams: filteredParams } : {}
2369
2772
  };
2370
- writeHooksConfig(updatedConfig);
2773
+ writeScopedHooksConfig(updatedConfig, configScope, cwd);
2371
2774
  try {
2372
2775
  const distinctId = getInstanceId();
2373
2776
  const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
@@ -2388,7 +2791,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
2388
2791
  console.log(`Remaining: ${remaining.length > 0 ? remaining.join(", ") : "(none)"}`);
2389
2792
  return;
2390
2793
  }
2391
- const configBeforeRemoval = readHooksConfig();
2794
+ const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
2392
2795
  const scopesToRemove = scope === "all" ? [...HOOK_SCOPES] : [scope];
2393
2796
  let totalRemoved = 0;
2394
2797
  for (const s of scopesToRemove) {
@@ -2435,10 +2838,18 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
2435
2838
  hostname_hash: hashToId(hostname())
2436
2839
  });
2437
2840
  } catch {}
2438
- if (scope === "all" || !HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
2439
- const existingForClear = readHooksConfig();
2440
- const { customPoliciesPath: _drop, policyParams: _dropParams, ...restClear } = existingForClear;
2441
- writeHooksConfig({ ...restClear, enabledPolicies: [] });
2841
+ if (scope === "all") {
2842
+ for (const s of HOOK_SCOPES) {
2843
+ const existing = readScopedHooksConfig(s, cwd);
2844
+ if (existing.enabledPolicies.length > 0 || existing.customPoliciesPath || existing.policyParams) {
2845
+ const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
2846
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
2847
+ }
2848
+ }
2849
+ } else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
2850
+ const existing = readScopedHooksConfig(configScope, cwd);
2851
+ const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
2852
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
2442
2853
  }
2443
2854
  }
2444
2855
  async function listHooks(cwd) {
@@ -2575,6 +2986,35 @@ Failproof AI Hook Policies
2575
2986
  }
2576
2987
  console.log();
2577
2988
  }
2989
+ const base = cwd ? resolve5(cwd) : process.cwd();
2990
+ const conventionDirs = [
2991
+ { label: "Project", dir: resolve5(base, ".failproofai", "policies") },
2992
+ { label: "User", dir: resolve5(homedir6(), ".failproofai", "policies") }
2993
+ ];
2994
+ for (const { label, dir } of conventionDirs) {
2995
+ const files = discoverPolicyFiles(dir);
2996
+ if (files.length === 0)
2997
+ continue;
2998
+ console.log(`
2999
+ ── Convention Policies — ${label} (${dir}) ──────────`);
3000
+ for (const file of files) {
3001
+ try {
3002
+ const hooks = await loadCustomHooks(file);
3003
+ if (hooks.length === 0) {
3004
+ const filename = basename2(file);
3005
+ console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
3006
+ } else {
3007
+ const filename = basename2(file);
3008
+ const hookSummary = hooks.map((h) => h.name).join(", ");
3009
+ console.log(` \x1B[32m✓\x1B[0m ${filename.padEnd(nameColWidth)}${hooks.length} hook(s): ${hookSummary}`);
3010
+ }
3011
+ } catch {
3012
+ const filename = basename2(file);
3013
+ console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31merror\x1B[0m`);
3014
+ }
3015
+ }
3016
+ console.log();
3017
+ }
2578
3018
  }
2579
3019
  var VALID_POLICY_NAMES;
2580
3020
  var init_manager = __esm(() => {
@@ -2590,10 +3030,10 @@ var init_manager = __esm(() => {
2590
3030
  });
2591
3031
 
2592
3032
  // lib/paths.ts
2593
- import { homedir as homedir6 } from "os";
3033
+ import { homedir as homedir7 } from "os";
2594
3034
  import { join as join4 } from "path";
2595
3035
  function getDefaultClaudeProjectsPath() {
2596
- return join4(homedir6(), ".claude", "projects");
3036
+ return join4(homedir7(), ".claude", "projects");
2597
3037
  }
2598
3038
  var init_paths = () => {};
2599
3039
 
@@ -2762,7 +3202,7 @@ import { realpathSync as realpathSync2 } from "fs";
2762
3202
  import { dirname as dirname5, resolve as resolve8 } from "path";
2763
3203
  import { fileURLToPath as fileURLToPath2 } from "url";
2764
3204
  // package.json
2765
- var version = "0.0.2";
3205
+ var version = "0.0.4-beta.0";
2766
3206
 
2767
3207
  // bin/failproofai.mjs
2768
3208
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
@@ -2826,6 +3266,11 @@ COMMANDS
2826
3266
  --version, -v Print version and exit
2827
3267
  --help, -h Show this help message
2828
3268
 
3269
+ CONVENTION POLICIES
3270
+ Drop *policies.{js,mjs,ts} files into .failproofai/policies/ for auto-loading.
3271
+ Works at project level (.failproofai/policies/) and user level (~/.failproofai/policies/).
3272
+ No --custom flag or config changes needed \u2014 just drop files and they're picked up.
3273
+
2829
3274
  EXAMPLES
2830
3275
  failproofai policies
2831
3276
  failproofai policies --install