failproofai 0.0.2 → 0.0.3

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 (483) 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]__12kr5~_._.js → [root-of-the-server]__03kiqd5._.js} +2 -2
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  66. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qo8503._.js → [root-of-the-server]__0bo8s~-._.js} +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +9 -9
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.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/{0x-625~1vx1lu.js → 02t9.s735hqyq.js} +1 -1
  90. package/.next/standalone/.next/static/chunks/{0ov60i6md~37t.js → 03oepxbqx6o8~.js} +2 -2
  91. package/.next/standalone/.next/static/chunks/{031pa5~qfzt~_.js → 09e7drilkf1sn.js} +1 -1
  92. package/.next/standalone/.next/static/chunks/{0y~0creqvl5wx.js → 0cwft44dh9bww.js} +1 -1
  93. package/.next/standalone/.next/static/chunks/{06og.7e9nkpjh.js → 0e2uz2g026ckb.js} +1 -1
  94. package/.next/standalone/.next/static/chunks/0gu_a.a80ritd.css +1 -0
  95. package/.next/standalone/.next/static/chunks/{15wf7x-e.8ia3.js → 0h5kbvg~.xf.v.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{0_4y_t03jn2nq.js → 0od..umlku4bb.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/{0cvffh-pbsv5u.js → 0xbwzy4dl87-0.js} +1 -1
  98. package/.next/standalone/.next/static/chunks/{0c_ljlxa._4lc.js → 18cl6wups7ouq.js} +2 -2
  99. package/.next/standalone/.next/static/chunks/{turbopack-0uc5y~g6h.n7-.js → turbopack-0r26pc8h0y_-e.js} +1 -1
  100. package/.next/standalone/CHANGELOG.md +103 -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 +539 -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 +566 -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 +49 -0
  117. package/.next/standalone/docs/ar/cli/list-policies.mdx +31 -0
  118. package/.next/standalone/docs/ar/cli/remove-policies.mdx +45 -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} +151 -13
  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 +48 -0
  135. package/.next/standalone/docs/cli/list-policies.mdx +31 -0
  136. package/.next/standalone/docs/cli/remove-policies.mdx +44 -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 +564 -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 +48 -0
  147. package/.next/standalone/docs/de/cli/list-policies.mdx +31 -0
  148. package/.next/standalone/docs/de/cli/remove-policies.mdx +44 -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 +938 -24
  160. package/.next/standalone/docs/es/architecture.mdx +332 -0
  161. package/.next/standalone/docs/es/built-in-policies.mdx +564 -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 +48 -0
  166. package/.next/standalone/docs/es/cli/list-policies.mdx +31 -0
  167. package/.next/standalone/docs/es/cli/remove-policies.mdx +44 -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 +564 -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 +48 -0
  186. package/.next/standalone/docs/fr/cli/list-policies.mdx +31 -0
  187. package/.next/standalone/docs/fr/cli/remove-policies.mdx +44 -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 +564 -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 +48 -0
  205. package/.next/standalone/docs/he/cli/list-policies.mdx +32 -0
  206. package/.next/standalone/docs/he/cli/remove-policies.mdx +44 -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 +564 -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 +48 -0
  223. package/.next/standalone/docs/hi/cli/list-policies.mdx +31 -0
  224. package/.next/standalone/docs/hi/cli/remove-policies.mdx +44 -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 +564 -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 +48 -0
  256. package/.next/standalone/docs/it/cli/list-policies.mdx +31 -0
  257. package/.next/standalone/docs/it/cli/remove-policies.mdx +44 -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 +562 -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 +48 -0
  274. package/.next/standalone/docs/ja/cli/list-policies.mdx +31 -0
  275. package/.next/standalone/docs/ja/cli/remove-policies.mdx +44 -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 +562 -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 +48 -0
  292. package/.next/standalone/docs/ko/cli/list-policies.mdx +31 -0
  293. package/.next/standalone/docs/ko/cli/remove-policies.mdx +44 -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 +564 -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 +48 -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 +44 -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 +562 -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 +44 -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 +562 -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 +48 -0
  350. package/.next/standalone/docs/tr/cli/list-policies.mdx +31 -0
  351. package/.next/standalone/docs/tr/cli/remove-policies.mdx +45 -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 +564 -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 +48 -0
  368. package/.next/standalone/docs/vi/cli/list-policies.mdx +31 -0
  369. package/.next/standalone/docs/vi/cli/remove-policies.mdx +44 -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 +562 -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 +48 -0
  386. package/.next/standalone/docs/zh/cli/list-policies.mdx +31 -0
  387. package/.next/standalone/docs/zh/cli/remove-policies.mdx +44 -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 +405 -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 +539 -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 +405 -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/15jpradyu_531.css +0 -1
  477. package/.next/standalone/docs/cli-reference.md +0 -175
  478. package/.next/standalone/docs/custom-hooks.md +0 -261
  479. package/.next/standalone/docs/getting-started.md +0 -128
  480. package/.next/standalone/docs/introduction.md +0 -47
  481. /package/.next/standalone/.next/static/{WS-OQSqL1Lp1w_obXfjvl → En9eEShUkUjgeYbY9v6Gy}/_buildManifest.js +0 -0
  482. /package/.next/standalone/.next/static/{WS-OQSqL1Lp1w_obXfjvl → En9eEShUkUjgeYbY9v6Gy}/_clientMiddlewareManifest.js +0 -0
  483. /package/.next/standalone/.next/static/{WS-OQSqL1Lp1w_obXfjvl → En9eEShUkUjgeYbY9v6Gy}/_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,75 @@ 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
+ beta: true
1330
+ },
1331
+ {
1332
+ name: "require-push-before-stop",
1333
+ description: "Require all commits to be pushed to remote before Claude stops",
1334
+ fn: requirePushBeforeStop,
1335
+ match: { events: ["Stop"] },
1336
+ defaultEnabled: false,
1337
+ category: "Workflow",
1338
+ beta: true,
1339
+ params: {
1340
+ remote: {
1341
+ type: "string",
1342
+ description: "Remote name to push to (default: origin)",
1343
+ default: "origin"
1344
+ },
1345
+ baseBranch: {
1346
+ type: "string",
1347
+ description: "Base branch to compare against (default: main)",
1348
+ default: "main"
1349
+ }
1350
+ }
1351
+ },
1352
+ {
1353
+ name: "require-pr-before-stop",
1354
+ description: "Require a pull request to exist for the current branch before Claude stops",
1355
+ fn: requirePrBeforeStop,
1356
+ match: { events: ["Stop"] },
1357
+ defaultEnabled: false,
1358
+ category: "Workflow",
1359
+ beta: true,
1360
+ params: {
1361
+ baseBranch: {
1362
+ type: "string",
1363
+ description: "Base branch to compare against (default: main)",
1364
+ default: "main"
1365
+ }
1366
+ }
1367
+ },
1368
+ {
1369
+ name: "require-ci-green-before-stop",
1370
+ description: "Require CI checks to pass on the current branch before Claude stops",
1371
+ fn: requireCiGreenBeforeStop,
1372
+ match: { events: ["Stop"] },
1373
+ defaultEnabled: false,
1374
+ category: "Workflow",
1375
+ beta: true
1105
1376
  }
1106
1377
  ];
1107
1378
  });
1108
1379
 
1109
1380
  // src/hooks/policy-evaluator.ts
1381
+ function appendHint(baseReason, hint) {
1382
+ const base = baseReason.trim();
1383
+ const normalizedHint = typeof hint === "string" ? hint.trim() : "";
1384
+ if (!normalizedHint)
1385
+ return base;
1386
+ if (!base)
1387
+ return normalizedHint;
1388
+ return `${base}. ${normalizedHint}`;
1389
+ }
1110
1390
  async function evaluatePolicies(eventType, payload, session, config) {
1111
1391
  const toolName = payload.tool_name;
1112
1392
  const toolInput = payload.tool_input;
@@ -1122,8 +1402,8 @@ async function evaluatePolicies(eventType, payload, session, config) {
1122
1402
  toolInput,
1123
1403
  session
1124
1404
  };
1125
- let instructPolicyName = null;
1126
- let instructReason = null;
1405
+ const instructEntries = [];
1406
+ const allowEntries = [];
1127
1407
  for (const policy of policies) {
1128
1408
  const schema = POLICY_PARAMS_MAP.get(policy.name);
1129
1409
  let ctx;
@@ -1145,7 +1425,7 @@ async function evaluatePolicies(eventType, payload, session, config) {
1145
1425
  continue;
1146
1426
  }
1147
1427
  if (result.decision === "deny") {
1148
- const reason = result.reason ?? `Blocked by policy: ${policy.name}`;
1428
+ const reason = appendHint(result.reason ?? `Blocked by policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1149
1429
  hookLogInfo(`deny by "${policy.name}": ${reason}`);
1150
1430
  const displayTool = ctx.toolName ?? "unknown tool";
1151
1431
  if (eventType === "PreToolUse") {
@@ -1184,44 +1464,63 @@ async function evaluatePolicies(eventType, payload, session, config) {
1184
1464
  return {
1185
1465
  exitCode: 2,
1186
1466
  stdout: "",
1187
- stderr: "",
1467
+ stderr: reason,
1188
1468
  policyName: policy.name,
1189
1469
  reason,
1190
1470
  decision: "deny"
1191
1471
  };
1192
1472
  }
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}`);
1473
+ if (result.decision === "instruct") {
1474
+ const reason = appendHint(result.reason ?? `Instruction from policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1475
+ instructEntries.push({ policyName: policy.name, reason });
1476
+ hookLogInfo(`instruct by "${policy.name}": ${reason}`);
1477
+ }
1478
+ if (result.decision === "allow" && result.reason) {
1479
+ allowEntries.push({ policyName: policy.name, reason: result.reason });
1197
1480
  }
1198
1481
  }
1199
- if (instructPolicyName && instructReason) {
1482
+ if (instructEntries.length > 0) {
1483
+ const combined = instructEntries.map((e) => e.reason).join(`
1484
+ `);
1485
+ const policyNames = instructEntries.map((e) => e.policyName);
1200
1486
  if (eventType === "Stop") {
1201
1487
  return {
1202
1488
  exitCode: 2,
1203
1489
  stdout: "",
1204
- stderr: instructReason,
1205
- policyName: instructPolicyName,
1206
- reason: instructReason,
1490
+ stderr: combined,
1491
+ policyName: policyNames[0],
1492
+ policyNames,
1493
+ reason: combined,
1207
1494
  decision: "instruct"
1208
1495
  };
1209
1496
  }
1210
1497
  const response = {
1211
1498
  hookSpecificOutput: {
1212
1499
  hookEventName: eventType,
1213
- additionalContext: `Instruction from failproofai: ${instructReason}`
1500
+ additionalContext: `Instruction from failproofai: ${combined}`
1214
1501
  }
1215
1502
  };
1216
1503
  return {
1217
1504
  exitCode: 0,
1218
1505
  stdout: JSON.stringify(response),
1219
1506
  stderr: "",
1220
- policyName: instructPolicyName,
1221
- reason: instructReason,
1507
+ policyName: policyNames[0],
1508
+ policyNames,
1509
+ reason: combined,
1222
1510
  decision: "instruct"
1223
1511
  };
1224
1512
  }
1513
+ if (allowEntries.length > 0) {
1514
+ const combined = allowEntries.map((e) => e.reason).join(`
1515
+ `);
1516
+ const policyNames = allowEntries.map((e) => e.policyName);
1517
+ const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit";
1518
+ const response = supportsHookSpecificOutput ? { hookSpecificOutput: { hookEventName: eventType, additionalContext: `Note from failproofai: ${combined}` } } : { reason: combined };
1519
+ const stderrMsg = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
1520
+ `);
1521
+ return { exitCode: 0, stdout: JSON.stringify(response), stderr: stderrMsg + `
1522
+ `, policyName: policyNames[0], policyNames, reason: combined, decision: "allow" };
1523
+ }
1225
1524
  return { exitCode: 0, stdout: "", stderr: "", policyName: null, reason: null, decision: "allow" };
1226
1525
  }
1227
1526
  var POLICY_PARAMS_MAP;
@@ -1291,10 +1590,9 @@ async function createEsmShim(distIndex, distUrl) {
1291
1590
  const shimPath = distIndex + ".__failproofai_esm_shim__.mjs";
1292
1591
  const shimCode = [
1293
1592
  `import _cjs from '${distUrl}';`,
1294
- `export const createApp = _cjs.createApp;`,
1295
- `export const getQueueCondition = _cjs.getQueueCondition;`,
1296
- `export const clearQueueCondition = _cjs.clearQueueCondition;`,
1297
1593
  `export const customPolicies = _cjs.customPolicies;`,
1594
+ `export const getCustomHooks = _cjs.getCustomHooks;`,
1595
+ `export const clearCustomHooks = _cjs.clearCustomHooks;`,
1298
1596
  `export const allow = _cjs.allow;`,
1299
1597
  `export const deny = _cjs.deny;`,
1300
1598
  `export const instruct = _cjs.instruct;`,
@@ -1374,20 +1672,21 @@ var init_loader_utils = __esm(() => {
1374
1672
  });
1375
1673
 
1376
1674
  // src/hooks/custom-hooks-loader.ts
1377
- import { resolve as resolve4, isAbsolute } from "node:path";
1378
- import { existsSync as existsSync3 } from "node:fs";
1675
+ import { resolve as resolve4, isAbsolute, basename } from "node:path";
1676
+ import { existsSync as existsSync3, readdirSync } from "node:fs";
1379
1677
  import { pathToFileURL as pathToFileURL2 } from "node:url";
1380
- async function loadCustomHooks(customPoliciesPath, opts) {
1381
- if (!customPoliciesPath)
1678
+ import { homedir as homedir4 } from "node:os";
1679
+ function discoverPolicyFiles(dir) {
1680
+ if (!existsSync3(dir))
1382
1681
  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}`);
1682
+ try {
1683
+ const entries = readdirSync(dir, { withFileTypes: true });
1684
+ 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));
1685
+ } catch {
1388
1686
  return [];
1389
1687
  }
1390
- clearCustomHooks();
1688
+ }
1689
+ async function loadSingleFile(absPath, opts) {
1391
1690
  const g = globalThis;
1392
1691
  g[LOADING_KEY] = true;
1393
1692
  let tmpFiles = [];
@@ -1403,18 +1702,93 @@ async function loadCustomHooks(customPoliciesPath, opts) {
1403
1702
  if (opts?.strict)
1404
1703
  throw new Error(`Failed to load custom hooks from ${absPath}: ${msg}`);
1405
1704
  hookLogError(`failed to load custom hooks from ${absPath}: ${msg}`);
1406
- return [];
1407
1705
  } finally {
1408
1706
  g[LOADING_KEY] = false;
1409
1707
  await cleanupTmpFiles(tmpFiles);
1410
1708
  }
1709
+ }
1710
+ async function loadCustomHooks(customPoliciesPath, opts) {
1711
+ if (!customPoliciesPath)
1712
+ return [];
1713
+ const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1714
+ if (!existsSync3(absPath)) {
1715
+ if (opts?.strict)
1716
+ throw new Error(`Custom hooks file not found: ${absPath}`);
1717
+ hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1718
+ return [];
1719
+ }
1720
+ clearCustomHooks();
1721
+ await loadSingleFile(absPath, opts);
1411
1722
  return getCustomHooks();
1412
1723
  }
1413
- var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__";
1724
+ async function loadAllCustomHooks(customPoliciesPath, opts) {
1725
+ clearCustomHooks();
1726
+ const conventionSources = [];
1727
+ if (customPoliciesPath) {
1728
+ const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1729
+ if (existsSync3(absPath)) {
1730
+ await loadSingleFile(absPath);
1731
+ } else {
1732
+ hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1733
+ }
1734
+ }
1735
+ const hooksBeforeConvention = getCustomHooks().length;
1736
+ const projectDir = resolve4(opts?.sessionCwd ?? process.cwd(), ".failproofai", "policies");
1737
+ const projectFiles = discoverPolicyFiles(projectDir);
1738
+ for (const file of projectFiles) {
1739
+ const hooksBefore = getCustomHooks().length;
1740
+ await loadSingleFile(file);
1741
+ const newHooks = getCustomHooks().slice(hooksBefore);
1742
+ if (newHooks.length > 0) {
1743
+ conventionSources.push({
1744
+ scope: "project",
1745
+ file: basename(file),
1746
+ hookNames: newHooks.map((h) => h.name)
1747
+ });
1748
+ }
1749
+ }
1750
+ const userDir = resolve4(homedir4(), ".failproofai", "policies");
1751
+ const userFiles = discoverPolicyFiles(userDir);
1752
+ for (const file of userFiles) {
1753
+ const hooksBefore = getCustomHooks().length;
1754
+ await loadSingleFile(file);
1755
+ const newHooks = getCustomHooks().slice(hooksBefore);
1756
+ if (newHooks.length > 0) {
1757
+ conventionSources.push({
1758
+ scope: "user",
1759
+ file: basename(file),
1760
+ hookNames: newHooks.map((h) => h.name)
1761
+ });
1762
+ }
1763
+ }
1764
+ const allHooks = getCustomHooks();
1765
+ const conventionCount = allHooks.length - hooksBeforeConvention;
1766
+ if (projectFiles.length > 0 || userFiles.length > 0) {
1767
+ hookLogInfo(`convention policies: ${projectFiles.length} project file(s), ${userFiles.length} user file(s), ${conventionCount} hook(s)`);
1768
+ }
1769
+ const hookNameToScope = new Map;
1770
+ for (const source of conventionSources) {
1771
+ for (const name of source.hookNames) {
1772
+ hookNameToScope.set(name, source.scope);
1773
+ }
1774
+ }
1775
+ const conventionHookRefs = new Set;
1776
+ for (const hook of allHooks.slice(hooksBeforeConvention)) {
1777
+ conventionHookRefs.add(hook);
1778
+ }
1779
+ for (const hook of allHooks) {
1780
+ if (conventionHookRefs.has(hook)) {
1781
+ hook.__conventionScope = hookNameToScope.get(hook.name) ?? "project";
1782
+ }
1783
+ }
1784
+ return { hooks: allHooks, conventionSources };
1785
+ }
1786
+ var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__", CONVENTION_FILE_RE;
1414
1787
  var init_custom_hooks_loader = __esm(() => {
1415
1788
  init_hook_logger();
1416
1789
  init_custom_hooks_registry();
1417
1790
  init_loader_utils();
1791
+ CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
1418
1792
  });
1419
1793
 
1420
1794
  // src/hooks/hook-activity-store.ts
@@ -1423,14 +1797,14 @@ import {
1423
1797
  writeFileSync as writeFileSync2,
1424
1798
  appendFileSync as appendFileSync2,
1425
1799
  renameSync as renameSync2,
1426
- readdirSync,
1800
+ readdirSync as readdirSync2,
1427
1801
  mkdirSync as mkdirSync3,
1428
1802
  existsSync as existsSync4,
1429
1803
  statSync as statSync2,
1430
1804
  unlinkSync
1431
1805
  } from "node:fs";
1432
1806
  import { join as join3 } from "node:path";
1433
- import { homedir as homedir4 } from "node:os";
1807
+ import { homedir as homedir5 } from "node:os";
1434
1808
  function ensureDir() {
1435
1809
  if (!existsSync4(storeDir)) {
1436
1810
  mkdirSync3(storeDir, { recursive: true });
@@ -1516,7 +1890,11 @@ function updateStats(entry) {
1516
1890
  s.totalEvents += 1;
1517
1891
  if (entry.decision === "deny")
1518
1892
  s.denyCount += 1;
1519
- if (entry.policyName) {
1893
+ if (entry.policyNames && entry.policyNames.length > 0) {
1894
+ for (const name of entry.policyNames) {
1895
+ s.policyMap[name] = (s.policyMap[name] ?? 0) + 1;
1896
+ }
1897
+ } else if (entry.policyName) {
1520
1898
  s.policyMap[entry.policyName] = (s.policyMap[entry.policyName] ?? 0) + 1;
1521
1899
  }
1522
1900
  const tmpPath = join3(storeDir, `stats.json.${process.pid}.tmp`);
@@ -1531,12 +1909,12 @@ function updateStats(entry) {
1531
1909
  }
1532
1910
  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
1911
  var init_hook_activity_store = __esm(() => {
1534
- DEFAULT_STORE_DIR = join3(homedir4(), ".failproofai", "cache", "hook-activity");
1912
+ DEFAULT_STORE_DIR = join3(homedir5(), ".failproofai", "cache", "hook-activity");
1535
1913
  storeDir = DEFAULT_STORE_DIR;
1536
1914
  });
1537
1915
 
1538
1916
  // package.json
1539
- var version2 = "0.0.2";
1917
+ var version2 = "0.0.3";
1540
1918
  var init_package = () => {};
1541
1919
 
1542
1920
  // src/posthog-key.ts
@@ -1698,9 +2076,14 @@ async function handleHookEvent(eventType) {
1698
2076
  const config = readMergedHooksConfig(session.cwd);
1699
2077
  clearPolicies();
1700
2078
  registerBuiltinPolicies(config.enabledPolicies);
1701
- const customHooksList = await loadCustomHooks(config.customPoliciesPath);
2079
+ const loadResult = await loadAllCustomHooks(config.customPoliciesPath, { sessionCwd: session.cwd });
2080
+ const customHooksList = loadResult.hooks;
2081
+ const conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
1702
2082
  for (const hook of customHooksList) {
1703
2083
  const hookName = hook.name;
2084
+ const conventionScope = hook.__conventionScope;
2085
+ const isConvention = !!conventionScope;
2086
+ const prefix = isConvention ? `.failproofai-${conventionScope}` : "custom";
1704
2087
  const fn = async (ctx) => {
1705
2088
  try {
1706
2089
  const result2 = await Promise.race([
@@ -1711,16 +2094,18 @@ async function handleHookEvent(eventType) {
1711
2094
  } catch (err) {
1712
2095
  const msg = err instanceof Error ? err.message : String(err);
1713
2096
  const isTimeout = msg === "timeout";
1714
- hookLogWarn(`custom hook "${hookName}" failed: ${msg}`);
2097
+ hookLogWarn(`${prefix} hook "${hookName}" failed: ${msg}`);
1715
2098
  trackHookEvent(getInstanceId(), "custom_hook_error", {
1716
2099
  hook_name: hookName,
1717
2100
  error_type: isTimeout ? "timeout" : "exception",
1718
- event_type: eventType
2101
+ event_type: eventType,
2102
+ is_convention_policy: isConvention,
2103
+ convention_scope: conventionScope ?? null
1719
2104
  });
1720
2105
  return { decision: "allow" };
1721
2106
  }
1722
2107
  };
1723
- registerPolicy(`custom/${hookName}`, hook.description ?? "", fn, hook.match ?? {}, -1);
2108
+ registerPolicy(`${prefix}/${hookName}`, hook.description ?? "", fn, hook.match ?? {}, -1);
1724
2109
  }
1725
2110
  if (customHooksList.length > 0) {
1726
2111
  trackHookEvent(getInstanceId(), "custom_hooks_loaded", {
@@ -1729,7 +2114,16 @@ async function handleHookEvent(eventType) {
1729
2114
  event_types_covered: [...new Set(customHooksList.flatMap((h) => h.match?.events ?? []))]
1730
2115
  });
1731
2116
  }
1732
- hookLogInfo(`event=${eventType} policies=${config.enabledPolicies.length} custom=${customHooksList.length}`);
2117
+ if (loadResult.conventionSources.length > 0) {
2118
+ trackHookEvent(getInstanceId(), "convention_policies_loaded", {
2119
+ event_type: eventType,
2120
+ project_file_count: loadResult.conventionSources.filter((s) => s.scope === "project").length,
2121
+ user_file_count: loadResult.conventionSources.filter((s) => s.scope === "user").length,
2122
+ convention_hook_count: conventionHookNames.size,
2123
+ convention_hook_names: [...conventionHookNames]
2124
+ });
2125
+ }
2126
+ hookLogInfo(`event=${eventType} policies=${config.enabledPolicies.length} custom=${customHooksList.length} convention=${conventionHookNames.size}`);
1733
2127
  const result = await evaluatePolicies(eventType, parsed, session, config);
1734
2128
  const durationMs = Math.round(performance.now() - startTime);
1735
2129
  hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
@@ -1745,6 +2139,7 @@ async function handleHookEvent(eventType) {
1745
2139
  eventType,
1746
2140
  toolName: parsed.tool_name ?? null,
1747
2141
  policyName: result.policyName,
2142
+ policyNames: result.policyNames,
1748
2143
  decision: result.decision,
1749
2144
  reason: result.reason,
1750
2145
  durationMs,
@@ -1760,7 +2155,9 @@ async function handleHookEvent(eventType) {
1760
2155
  if (result.decision === "deny" || result.decision === "instruct") {
1761
2156
  try {
1762
2157
  const isCustomHook = result.policyName?.startsWith("custom/") ?? false;
1763
- const hasCustomParams = !isCustomHook && !!(result.policyName && config.policyParams?.[result.policyName]);
2158
+ const isConventionPolicy = result.policyName?.startsWith(".failproofai-") ?? false;
2159
+ const conventionScope = isConventionPolicy ? result.policyName.match(/^\.failproofai-(project|user)\//)?.[1] ?? null : null;
2160
+ const hasCustomParams = !isCustomHook && !isConventionPolicy && !!(result.policyName && config.policyParams?.[result.policyName]);
1764
2161
  const paramKeysOverridden = hasCustomParams ? Object.keys(config.policyParams[result.policyName]) : [];
1765
2162
  const distinctId = getInstanceId();
1766
2163
  await trackHookEvent(distinctId, "hook_policy_triggered", {
@@ -1769,6 +2166,8 @@ async function handleHookEvent(eventType) {
1769
2166
  policy_name: result.policyName,
1770
2167
  decision: result.decision,
1771
2168
  is_custom_hook: isCustomHook,
2169
+ is_convention_policy: isConventionPolicy,
2170
+ convention_scope: conventionScope,
1772
2171
  has_custom_params: hasCustomParams,
1773
2172
  param_keys_overridden: paramKeysOverridden
1774
2173
  });
@@ -2101,13 +2500,13 @@ __export(exports_manager, {
2101
2500
  });
2102
2501
  import { execSync as execSync3 } from "node:child_process";
2103
2502
  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";
2503
+ import { resolve as resolve5, dirname as dirname3, basename as basename2 } from "node:path";
2504
+ import { homedir as homedir6, platform, arch, release, hostname } from "node:os";
2106
2505
  function getSettingsPath(scope, cwd) {
2107
2506
  const base = cwd ? resolve5(cwd) : process.cwd();
2108
2507
  switch (scope) {
2109
2508
  case "user":
2110
- return resolve5(homedir5(), ".claude", "settings.json");
2509
+ return resolve5(homedir6(), ".claude", "settings.json");
2111
2510
  case "project":
2112
2511
  return resolve5(base, ".claude", "settings.json");
2113
2512
  case "local":
@@ -2234,7 +2633,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
2234
2633
  }
2235
2634
  }
2236
2635
  const binaryPath = resolveFailproofaiBinary();
2237
- const previousConfig = readHooksConfig();
2636
+ const previousConfig = readScopedHooksConfig(scope, cwd);
2238
2637
  const previousEnabled = new Set(previousConfig.enabledPolicies);
2239
2638
  let selectedPolicies;
2240
2639
  if (policyNames !== undefined) {
@@ -2268,7 +2667,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
2268
2667
  console.log(`
2269
2668
  Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
2270
2669
  }
2271
- writeHooksConfig(configToWrite);
2670
+ writeScopedHooksConfig(configToWrite, scope, cwd);
2272
2671
  console.log(`
2273
2672
  Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`);
2274
2673
  if (removeCustomHooks) {
@@ -2282,7 +2681,7 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
2282
2681
  settings.hooks = {};
2283
2682
  }
2284
2683
  for (const eventType of HOOK_EVENT_TYPES) {
2285
- const command = `"${binaryPath}" --hook ${eventType}`;
2684
+ const command = scope === "project" ? `npx -y failproofai --hook ${eventType}` : `"${binaryPath}" --hook ${eventType}`;
2286
2685
  const hookEntry = {
2287
2686
  type: "command",
2288
2687
  command,
@@ -2327,12 +2726,19 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
2327
2726
  hostname_hash: hashToId(hostname()),
2328
2727
  has_custom_hooks_path: !!configToWrite.customPoliciesPath,
2329
2728
  has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
2330
- param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : []
2729
+ param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
2730
+ command_format: scope === "project" ? "npx" : "absolute"
2331
2731
  });
2332
2732
  } catch {}
2333
2733
  console.log(`Failproof AI hooks installed for all ${HOOK_EVENT_TYPES.length} event types (scope: ${scope}).`);
2334
2734
  console.log(`Settings: ${settingsPath}`);
2335
- console.log(`Binary: ${binaryPath}`);
2735
+ if (scope === "project") {
2736
+ console.log(`Command: npx -y failproofai`);
2737
+ console.log(`
2738
+ This file can be committed to git — no machine-specific paths.`);
2739
+ } else {
2740
+ console.log(`Binary: ${binaryPath}`);
2741
+ }
2336
2742
  const otherScopes = deduplicateScopes(HOOK_SCOPES, cwd).filter((s) => s !== scope);
2337
2743
  const duplicates = otherScopes.filter((s) => hooksInstalledInSettings(s, cwd));
2338
2744
  if (duplicates.length > 0) {
@@ -2345,15 +2751,16 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
2345
2751
  }
2346
2752
  }
2347
2753
  async function removeHooks(policyNames, scope = "user", cwd, opts) {
2754
+ const configScope = scope === "all" ? "user" : scope;
2348
2755
  if (opts?.removeCustomHooks) {
2349
- const config = readHooksConfig();
2756
+ const config = readScopedHooksConfig(configScope, cwd);
2350
2757
  delete config.customPoliciesPath;
2351
- writeHooksConfig(config);
2758
+ writeScopedHooksConfig(config, configScope, cwd);
2352
2759
  console.log("Custom hooks path cleared.");
2353
2760
  }
2354
2761
  if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
2355
2762
  validatePolicyNames(policyNames);
2356
- const config = readHooksConfig();
2763
+ const config = readScopedHooksConfig(configScope, cwd);
2357
2764
  const removeSet = new Set(policyNames);
2358
2765
  const remaining = config.enabledPolicies.filter((p) => !removeSet.has(p));
2359
2766
  const notEnabled = policyNames.filter((p) => !config.enabledPolicies.includes(p));
@@ -2367,7 +2774,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
2367
2774
  enabledPolicies: remaining,
2368
2775
  ...filteredParams && Object.keys(filteredParams).length > 0 ? { policyParams: filteredParams } : {}
2369
2776
  };
2370
- writeHooksConfig(updatedConfig);
2777
+ writeScopedHooksConfig(updatedConfig, configScope, cwd);
2371
2778
  try {
2372
2779
  const distinctId = getInstanceId();
2373
2780
  const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
@@ -2388,7 +2795,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
2388
2795
  console.log(`Remaining: ${remaining.length > 0 ? remaining.join(", ") : "(none)"}`);
2389
2796
  return;
2390
2797
  }
2391
- const configBeforeRemoval = readHooksConfig();
2798
+ const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
2392
2799
  const scopesToRemove = scope === "all" ? [...HOOK_SCOPES] : [scope];
2393
2800
  let totalRemoved = 0;
2394
2801
  for (const s of scopesToRemove) {
@@ -2435,10 +2842,18 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
2435
2842
  hostname_hash: hashToId(hostname())
2436
2843
  });
2437
2844
  } 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: [] });
2845
+ if (scope === "all") {
2846
+ for (const s of HOOK_SCOPES) {
2847
+ const existing = readScopedHooksConfig(s, cwd);
2848
+ if (existing.enabledPolicies.length > 0 || existing.customPoliciesPath || existing.policyParams) {
2849
+ const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
2850
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
2851
+ }
2852
+ }
2853
+ } else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
2854
+ const existing = readScopedHooksConfig(configScope, cwd);
2855
+ const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
2856
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
2442
2857
  }
2443
2858
  }
2444
2859
  async function listHooks(cwd) {
@@ -2575,6 +2990,35 @@ Failproof AI Hook Policies
2575
2990
  }
2576
2991
  console.log();
2577
2992
  }
2993
+ const base = cwd ? resolve5(cwd) : process.cwd();
2994
+ const conventionDirs = [
2995
+ { label: "Project", dir: resolve5(base, ".failproofai", "policies") },
2996
+ { label: "User", dir: resolve5(homedir6(), ".failproofai", "policies") }
2997
+ ];
2998
+ for (const { label, dir } of conventionDirs) {
2999
+ const files = discoverPolicyFiles(dir);
3000
+ if (files.length === 0)
3001
+ continue;
3002
+ console.log(`
3003
+ ── Convention Policies — ${label} (${dir}) ──────────`);
3004
+ for (const file of files) {
3005
+ try {
3006
+ const hooks = await loadCustomHooks(file);
3007
+ if (hooks.length === 0) {
3008
+ const filename = basename2(file);
3009
+ console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
3010
+ } else {
3011
+ const filename = basename2(file);
3012
+ const hookSummary = hooks.map((h) => h.name).join(", ");
3013
+ console.log(` \x1B[32m✓\x1B[0m ${filename.padEnd(nameColWidth)}${hooks.length} hook(s): ${hookSummary}`);
3014
+ }
3015
+ } catch {
3016
+ const filename = basename2(file);
3017
+ console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31merror\x1B[0m`);
3018
+ }
3019
+ }
3020
+ console.log();
3021
+ }
2578
3022
  }
2579
3023
  var VALID_POLICY_NAMES;
2580
3024
  var init_manager = __esm(() => {
@@ -2590,10 +3034,10 @@ var init_manager = __esm(() => {
2590
3034
  });
2591
3035
 
2592
3036
  // lib/paths.ts
2593
- import { homedir as homedir6 } from "os";
3037
+ import { homedir as homedir7 } from "os";
2594
3038
  import { join as join4 } from "path";
2595
3039
  function getDefaultClaudeProjectsPath() {
2596
- return join4(homedir6(), ".claude", "projects");
3040
+ return join4(homedir7(), ".claude", "projects");
2597
3041
  }
2598
3042
  var init_paths = () => {};
2599
3043
 
@@ -2762,7 +3206,7 @@ import { realpathSync as realpathSync2 } from "fs";
2762
3206
  import { dirname as dirname5, resolve as resolve8 } from "path";
2763
3207
  import { fileURLToPath as fileURLToPath2 } from "url";
2764
3208
  // package.json
2765
- var version = "0.0.2";
3209
+ var version = "0.0.3";
2766
3210
 
2767
3211
  // bin/failproofai.mjs
2768
3212
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
@@ -2826,6 +3270,11 @@ COMMANDS
2826
3270
  --version, -v Print version and exit
2827
3271
  --help, -h Show this help message
2828
3272
 
3273
+ CONVENTION POLICIES
3274
+ Drop *policies.{js,mjs,ts} files into .failproofai/policies/ for auto-loading.
3275
+ Works at project level (.failproofai/policies/) and user level (~/.failproofai/policies/).
3276
+ No --custom flag or config changes needed \u2014 just drop files and they're picked up.
3277
+
2829
3278
  EXAMPLES
2830
3279
  failproofai policies
2831
3280
  failproofai policies --install