failproofai 1.0.7 → 1.0.8

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 (268) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js +9 -7
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +6 -6
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +11 -10
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js +8 -7
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -6
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js +7 -6
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +52 -8
  63. package/.next/standalone/.next/server/app/settings/page.js +12 -7
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/{[externals]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__20kzpkf._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
  70. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  71. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  74. package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
  78. package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__02npjtd._.js → [root-of-the-server]__056wjo4._.js} +3 -3
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06pflha._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
  87. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0p-5p8u._.js → [root-of-the-server]__0n0xg95._.js} +3 -3
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__013jr2b._.js → [root-of-the-server]__0rwtwpm._.js} +3 -3
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0da85px._.js → [root-of-the-server]__11mayhe._.js} +3 -3
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12e7nhs._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13t1zkw._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
  100. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ftmoxc._.js → [root-of-the-server]__1pprgri._.js} +3 -3
  101. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1p2otjt._.js → [root-of-the-server]__1q4p5b8._.js} +3 -3
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/{_166t73i._.js → _0-vcssj._.js} +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/_13kfn90._.js +23 -0
  109. package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
  110. package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
  113. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  117. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
  119. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
  120. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
  122. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
  123. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +4 -2
  124. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
  130. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  131. package/.next/standalone/.next/server/pages/404.html +1 -1
  132. package/.next/standalone/.next/server/pages/500.html +1 -1
  133. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  134. package/.next/standalone/.next/server/server-reference-manifest.json +67 -23
  135. package/.next/standalone/.next/static/chunks/06dnzbolj00mc.js +6 -0
  136. package/.next/standalone/.next/static/chunks/0bhidk90e-07f.css +2 -0
  137. package/.next/standalone/.next/static/chunks/{2mdh397ghgnvv.js → 0m-9d6yn9hx4j.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/{0__8a7m868fvf.js → 0wbjy0zo-m7is.js} +1 -1
  139. package/.next/standalone/.next/static/chunks/16f3fa-lx38hk.js +1 -0
  140. package/.next/standalone/.next/static/chunks/{0o6qlkgubtoex.js → 21uv-uusw329x.js} +1 -1
  141. package/.next/standalone/.next/static/chunks/2g2tki08kdhie.js +1 -0
  142. package/.next/standalone/.next/static/chunks/2qdpj67x6ifk_.js +69 -0
  143. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
  144. package/.next/standalone/.next/static/chunks/3-nbtkhg9y-1j.js +1 -0
  145. package/.next/standalone/.next/static/chunks/355km0ihuqo1p.js +1 -0
  146. package/.next/standalone/.next/static/chunks/{13i7-9is-vhys.js → 3c3qbmosdjl6w.js} +1 -1
  147. package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
  148. package/.next/standalone/app/actions/get-jev-config.ts +604 -0
  149. package/.next/standalone/app/actions/pack-actions.ts +12 -0
  150. package/.next/standalone/app/actions/update-jev-config.ts +569 -0
  151. package/.next/standalone/app/components/jev-notices.tsx +96 -0
  152. package/.next/standalone/app/policies/hooks-client.tsx +14 -3
  153. package/.next/standalone/app/settings/jev-panel.tsx +630 -0
  154. package/.next/standalone/app/settings/page.tsx +20 -1
  155. package/.next/standalone/app/settings/settings-client.tsx +27 -1
  156. package/.next/standalone/app/settings/settings.css +79 -0
  157. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
  158. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
  159. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
  160. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
  161. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
  163. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
  164. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
  165. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
  166. package/.next/standalone/package.json +10 -10
  167. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  168. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  169. package/.next/standalone/sdk/typescript/CHANGELOG.md +18 -1
  170. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +0 -274
  171. package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
  172. package/.next/standalone/server.js +1 -1
  173. package/README.md +3 -2
  174. package/bin/failproofai.mjs +148 -4
  175. package/dist/cli.mjs +18599 -9478
  176. package/dist/index.js +19 -1
  177. package/dist/worker.mjs +10500 -3773
  178. package/package.json +10 -10
  179. package/pi-extension/index.ts +11 -0
  180. package/scripts/build-policy-pack.mjs +53 -2
  181. package/src/audit/features.ts +3 -2
  182. package/src/hooks/builtin-policies.ts +195 -8
  183. package/src/hooks/cloud-connection.ts +190 -1
  184. package/src/hooks/cloud-enrollment-cli.ts +57 -8
  185. package/src/hooks/cloud-introspect.ts +6 -0
  186. package/src/hooks/cloud-managed-policies.ts +22 -0
  187. package/src/hooks/configure-wizard.ts +26 -6
  188. package/src/hooks/custom-hooks-loader.ts +98 -10
  189. package/src/hooks/custom-hooks-registry.ts +45 -1
  190. package/src/hooks/effective-reviewers.ts +243 -0
  191. package/src/hooks/first-run-gate.ts +5 -0
  192. package/src/hooks/flush-cli.ts +35 -8
  193. package/src/hooks/fp-config.ts +266 -1
  194. package/src/hooks/fp-home.ts +23 -0
  195. package/src/hooks/fp-reset.ts +22 -4
  196. package/src/hooks/handler.ts +303 -7
  197. package/src/hooks/hook-activity-store.ts +118 -6
  198. package/src/hooks/hook-telemetry.ts +41 -0
  199. package/src/hooks/jev-activity.ts +385 -0
  200. package/src/hooks/jev-cli.ts +2172 -0
  201. package/src/hooks/jev-cloud-connection.ts +392 -0
  202. package/src/hooks/loader-utils.ts +6 -0
  203. package/src/hooks/manager.ts +45 -7
  204. package/src/hooks/pack-cli.ts +568 -37
  205. package/src/hooks/pack-failclosed.ts +3 -0
  206. package/src/hooks/pack-manifest.ts +489 -7
  207. package/src/hooks/pack-store.ts +194 -11
  208. package/src/hooks/policy-authority.ts +386 -0
  209. package/src/hooks/policy-catalog.ts +206 -0
  210. package/src/hooks/policy-evaluator.ts +963 -796
  211. package/src/hooks/policy-registry.ts +27 -1
  212. package/src/hooks/policy-reviewability.ts +258 -0
  213. package/src/hooks/policy-types.ts +128 -0
  214. package/src/hooks/semantic/combine.ts +581 -0
  215. package/src/hooks/semantic/compile.ts +176 -0
  216. package/src/hooks/semantic/decide.ts +513 -0
  217. package/src/hooks/semantic/envelope.ts +1255 -0
  218. package/src/hooks/semantic/evaluator.ts +556 -0
  219. package/src/hooks/semantic/facts.ts +387 -0
  220. package/src/hooks/semantic/intent.ts +1190 -0
  221. package/src/hooks/semantic/jev-client.ts +1155 -0
  222. package/src/hooks/semantic/jev-config.ts +1142 -0
  223. package/src/hooks/semantic/jev-review.ts +389 -0
  224. package/src/hooks/semantic/jev-stats.ts +289 -0
  225. package/src/hooks/semantic/jev-throttle.ts +427 -0
  226. package/src/hooks/semantic/pack-policies.ts +294 -0
  227. package/src/hooks/semantic/policies.ts +597 -0
  228. package/src/hooks/semantic/precondition-names.ts +60 -0
  229. package/src/hooks/semantic/preconditions.ts +58 -0
  230. package/src/hooks/semantic/redact.ts +2937 -0
  231. package/src/hooks/semantic/session-root.ts +116 -0
  232. package/src/hooks/semantic/types.ts +162 -0
  233. package/src/hooks/semver-precedence.ts +128 -0
  234. package/src/hooks/tui.ts +4 -0
  235. package/src/hooks/types.ts +1 -1
  236. package/src/hooks/worker-server.ts +121 -27
  237. package/src/index.ts +6 -0
  238. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
  239. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_r2rbg._.js +0 -24
  240. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
  241. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
  242. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
  243. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bd3mje._.js +0 -3
  244. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
  246. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
  247. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
  248. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
  249. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
  250. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
  252. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
  253. package/.next/standalone/.next/server/chunks/ssr/_0h_douw._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/ssr/_1-i_gzc._.js +0 -23
  255. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
  258. package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +0 -1
  259. package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +0 -6
  260. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
  261. package/.next/standalone/.next/static/chunks/2klitrtzpaoe0.js +0 -1
  262. package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +0 -2
  263. package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +0 -1
  264. package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +0 -69
  265. package/.next/standalone/.next/static/chunks/43ufqrz8qo3h-.js +0 -1
  266. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_buildManifest.js +0 -0
  267. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_clientMiddlewareManifest.js +0 -0
  268. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "1.0.7",
4
- "description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
3
+ "version": "1.0.8",
4
+ "description": "Observability and enforcement for AI agent harnesses. 40 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs",
7
7
  "failproofaid": "./bin/failproofaid-shim.mjs"
@@ -122,15 +122,15 @@
122
122
  "browserslist": "4.28.8"
123
123
  },
124
124
  "optionalDependencies": {
125
- "@failproofai/failproofaid-linux-x64": "1.0.7",
126
- "@failproofai/failproofaid-linux-arm64": "1.0.7",
127
- "@failproofai/failproofaid-darwin-x64": "1.0.7",
128
- "@failproofai/failproofaid-darwin-arm64": "1.0.7"
125
+ "@failproofai/failproofaid-linux-x64": "1.0.8",
126
+ "@failproofai/failproofaid-linux-arm64": "1.0.8",
127
+ "@failproofai/failproofaid-darwin-x64": "1.0.8",
128
+ "@failproofai/failproofaid-darwin-arm64": "1.0.8"
129
129
  },
130
130
  "failproofaidBinaries": {
131
- "linux-x64": "65acfcabaa4dda6d37d2958d4100c366a85f17c68d2250de6126e2717ca773e6",
132
- "linux-arm64": "cece883f87d53034003be8f0f293aa89bd0e69306d6032e17b3eb91f230b3ce2",
133
- "darwin-x64": "565aa3d560a05dd76e2eb843ddbad593d2d8df94c963bdeb1c7e2a8a3816d0c5",
134
- "darwin-arm64": "2b41601b53e77a31439e92d8be9be6b9872e3d4ad7e8664ff89cd2be4ed6c44d"
131
+ "linux-x64": "35b641f60e1724513fbb760db382123dad2b5fcdd67dda6373bda80651bd36bb",
132
+ "linux-arm64": "a058288f5d08f99612f053793e5c7f59264d3da97f3dbe769e401b0f8536702d",
133
+ "darwin-x64": "b4f3dbe4aea44e77cc10839647c0dc03a1b8b797dfb27e1d89a6a96554ef27b8",
134
+ "darwin-arm64": "fd42f28bf92001d39117b488a215578b966f07d316214744aca64a1888f2aed2"
135
135
  }
136
136
  }
@@ -404,6 +404,17 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
404
404
  session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
405
405
  cwd: resolveCwd(e.cwd),
406
406
  hook_event_name: "UserPromptSubmit",
407
+ // "interactive" | "rpc" | "extension": which channel the input came
408
+ // through, and the only mark Pi gives about who wrote the prompt. It
409
+ // names the channel, not the author — `pi -p "<text>"` reports
410
+ // `interactive` exactly as a person typing does — so the Jev evaluator
411
+ // uses it only in the direction it is reliable in (`piMachineTurn`,
412
+ // src/hooks/semantic/intent.ts): `extension`, another extension's
413
+ // `sendUserMessage()` whose text can be model-written or repo-derived,
414
+ // is refused, while `interactive`, `rpc` and an absent value are all
415
+ // recorded as the human's request. Policies read it too, and a later Pi
416
+ // build may say more here.
417
+ input_source: e.source,
407
418
  });
408
419
  if (decision.block) {
409
420
  console.error(`[failproofai] prompt blocked: ${decision.reason ?? "blocked by policy"}`);
@@ -31,6 +31,24 @@
31
31
  * that stops an agent disabling failproofai, it ships compiled in, and
32
32
  * `pack-manifest.ts` REFUSES any pack that declares `alwaysOn`. A pack containing
33
33
  * it would be rejected by our own loader — correctly.
34
+ *
35
+ * **Every policy states its authority** — whether Jev may clear its verdict —
36
+ * as the registry would resolve it (`manifestAuthority`), not as a copy of
37
+ * whatever the catalog happened to spell. A machine running this pack reads
38
+ * authority from the MANIFEST, so a field left off here is a policy that is
39
+ * silently hard everywhere it is installed. The entry passes the same fields to
40
+ * `customPolicies.add`, so a manifest rebuilt from the entry (`failproofai
41
+ * publish`) says the same thing.
42
+ *
43
+ * **It emits no `semantic` array, deliberately.** A pack that declares one
44
+ * REPLACES this build's compiled-in semantic set wholesale
45
+ * (`semantic/pack-policies.ts`), and this pack is the dress rehearsal for the
46
+ * regex floor leaving the package — not for Jev's question set leaving it. So
47
+ * the 39 builtins travel and the 16 semantic policies stay compiled in, which
48
+ * also keeps `manifestAuthority` below judging `reviewedBy` against the right
49
+ * list: the builtin reviewer names, which is what a machine installing this pack
50
+ * will still be running. The separate `FailproofAI/jev-policies` pack is where
51
+ * the semantic half ships, and it declares its own `minCliVersion`.
34
52
  */
35
53
  import { execFileSync } from "node:child_process";
36
54
  import { createHash } from "node:crypto";
@@ -40,6 +58,7 @@ import { fileURLToPath } from "node:url";
40
58
  // The one place the pack's id is decided. Imported rather than restated — see
41
59
  // PACK_ID below for what restating it cost.
42
60
  import { CORE_SOURCE } from "../src/hooks/pack-store.ts";
61
+ import { manifestAuthority } from "../src/hooks/policy-authority.ts";
43
62
 
44
63
  const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
45
64
  // `--out <dir>` lets the conformance test generate into a temp directory rather
@@ -95,6 +114,23 @@ for (const policy of BUILTIN_POLICIES) {
95
114
  defaultEnabled: policy.defaultEnabled === true,
96
115
  match: policy.match,
97
116
  fn: policy.fn,
117
+ // The parameter schema. It has to be registered here as well as written
118
+ // into the manifest this script builds, because there are TWO manifest
119
+ // producers for this pack and they have to agree: this script builds one
120
+ // from POLICY_CATALOG, and the publish command rebuilds one by importing
121
+ // the entry and reading back what it registered. A field the entry does
122
+ // not register is a field the second producer cannot know about.
123
+ //
124
+ // params is the one that fails silently STRICTER. Registration reads a
125
+ // pack policy's schema from the manifest, so an absent schema means the
126
+ // policy sees empty params: block-sudo's allowPatterns, and block-rm-rf's
127
+ // and block-read-outside-cwd's allowPaths, stop applying -- taking the
128
+ // user's own configured values with them. No backticks in this comment:
129
+ // see the note above entrySource.
130
+ ...(policy.params !== undefined ? { params: policy.params } : {}),
131
+ // Whether Jev may clear it; see the note on authority at the top.
132
+ ...(policy.authority !== undefined ? { authority: policy.authority } : {}),
133
+ ...(policy.reviewedBy !== undefined ? { reviewedBy: policy.reviewedBy } : {}),
98
134
  });
99
135
  }
100
136
  `;
@@ -120,7 +156,20 @@ try {
120
156
  // validates these with the loader's own rules, so a shape the loader would
121
157
  // refuse fails this build rather than shipping.
122
158
  const { POLICY_CATALOG } = await import(join(ROOT, "src/hooks/policy-catalog.ts"));
123
- const policies = POLICY_CATALOG.filter((p) => !p.alwaysOn).map((p) => ({ ...p }));
159
+ const policies = POLICY_CATALOG.filter((p) => !p.alwaysOn).map((p) => {
160
+ // Throws on a declaration the registry would quietly downgrade: in the
161
+ // catalog that is a typo or a renamed semantic policy, and publishing it
162
+ // would turn a reviewed decision into an unannounced hard one. The fields
163
+ // come from the same call, so the check cannot be skipped without the
164
+ // manifest losing its authority too.
165
+ const resolved = manifestAuthority(p);
166
+ // Spread first so every field keeps its catalog position; only the two
167
+ // authority fields are replaced by their resolved values.
168
+ const entry = { ...p, authority: resolved.authority };
169
+ if (resolved.reviewedBy) entry.reviewedBy = resolved.reviewedBy;
170
+ else delete entry.reviewedBy;
171
+ return entry;
172
+ });
124
173
 
125
174
  const manifest = JSON.stringify(
126
175
  { id: PACK_ID, version, effect: "enforce", policies },
@@ -138,8 +187,10 @@ writeFileSync(
138
187
  );
139
188
 
140
189
  const omitted = POLICY_CATALOG.filter((p) => p.alwaysOn).map((p) => p.name);
190
+ const reviewable = policies.filter((p) => p.authority === "reviewable").length;
141
191
  console.log(
142
- `[policy-pack] ${PACK_ID}@${version} — ${policies.length} policies, ` +
192
+ `[policy-pack] ${PACK_ID}@${version} — ${policies.length} policies ` +
193
+ `(${reviewable} reviewable by Jev, ${policies.length - reviewable} hard), ` +
143
194
  `${(entryBytes.length / 1024).toFixed(1)} KB entry` +
144
195
  (omitted.length ? `; omitted alwaysOn: ${omitted.join(", ")}` : ""),
145
196
  );
@@ -69,13 +69,13 @@ export const ARCHITECT_CAUTION_SIGNALS = new Set(["reread-after-edit", "redundan
69
69
 
70
70
  /**
71
71
  * Mapping from policy/detector short-name → which archetype its hits feed,
72
- * and how heavily (intensity within the cluster). Every one of the 39 builtin
72
+ * and how heavily (intensity within the cluster). Every one of the 40 builtin
73
73
  * policies and 8 audit-only detectors maps exactly once — no overlaps, full
74
74
  * coverage. Weights express *severity within* a persona; cross-persona
75
75
  * fairness is handled later by lift normalisation, not by these numbers.
76
76
  */
77
77
  export const SIGNAL_MAP: Record<string, { archetype: ArchetypeKey; weight: number }> = {
78
- // ── cowboy ── destructive / forceful / bypasses guardrails (20) ───────────
78
+ // ── cowboy ── destructive / forceful / bypasses guardrails (21) ───────────
79
79
  "block-rm-rf": { archetype: "cowboy", weight: 2.0 },
80
80
  "block-failproofai-commands":{ archetype: "cowboy", weight: 2.0 },
81
81
  "block-sudo": { archetype: "cowboy", weight: 1.5 },
@@ -88,6 +88,7 @@ export const SIGNAL_MAP: Record<string, { archetype: ArchetypeKey; weight: numbe
88
88
  "git-commit-no-verify": { archetype: "cowboy", weight: 1.5 },
89
89
  "warn-git-amend": { archetype: "cowboy", weight: 0.8 },
90
90
  "warn-git-stash-drop": { archetype: "cowboy", weight: 1.0 },
91
+ "warn-git-clean": { archetype: "cowboy", weight: 1.0 },
91
92
  "warn-all-files-staged": { archetype: "cowboy", weight: 0.6 },
92
93
  "block-kubectl": { archetype: "cowboy", weight: 1.5 },
93
94
  "block-terraform": { archetype: "cowboy", weight: 1.5 },
@@ -186,6 +186,15 @@ const PUBLISH_CMD_RE = /(?:npm\s+publish|bun\s+publish|pnpm\s+publish|yarn\s+npm
186
186
  const ENV_PRINTENV_RE = /(?:^|\s|;|&&|\|\|)(?:env|printenv)(?:\s|$|;|&&|\|)/;
187
187
  const ECHO_ENV_RE = /echo\s+.*\$\{?[A-Za-z_]/;
188
188
  const EXPORT_RE = /(?:^|\s|;|&&|\|\|)export\s+\w+/;
189
+ // The other whole-environment dumps: bare `set` / `export` / `export -p` in
190
+ // command position (`set -e`, `export FOO=bar` are not), `declare|typeset` with
191
+ // -x or -p (not `declare -a`), `compgen -v|-e`, and /proc/<pid>/environ.
192
+ const ENV_DUMP_RE =
193
+ /(?:^|[;&|({\n`'"]|\$\()\s*(?:set|export(?:\s+-p)?)\s*(?:$|[|;&>)\n])|\b(?:declare|typeset)\s+-[A-Za-z]*[xp]|\bcompgen\s+-[A-Za-z]*[ev]|\/proc\/(?:self|thread-self|\d+|\$\$|\$BASHPID)\/environ/;
194
+ // A script dumping the environment OBJECT, not one variable of it. Two tests,
195
+ // both linear: an interpreter in the command, and the object used whole.
196
+ const SCRIPT_INTERP_RE = /\b(?:python[\d.]*|node|bun|deno)\b/;
197
+ const ENV_OBJECT_RE = /\bos\.environ(?!\s*\[|\.(?:get|setdefault|pop|update)\b)|\bprocess\.env(?![.\[\w])/;
189
198
  const PS_ENV_VAR_RE = /\$env:[A-Za-z_]/i;
190
199
  const PS_CHILDITEM_ENV_RE = /(?:Get-ChildItem|dir|gci|ls)\s+Env:/i;
191
200
  const DOTNET_GETENV_RE = /\[Environment\]::GetEnvironment/i;
@@ -623,8 +632,15 @@ const GIT_DESTRUCTIVE_SUBCOMMANDS = new Set([
623
632
  */
624
633
  const GIT_FLAGS_WITH_OPERANDS = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--exec-path"]);
625
634
 
626
- /** The `git` subcommand, with git's own global options walked off first. */
627
- function gitSubcommand(args: string[]): string | undefined {
635
+ /**
636
+ * Where the `git` subcommand sits in `args`, with git's own global options
637
+ * walked off first, or `-1` when there is no subcommand at all.
638
+ *
639
+ * The index rather than the value, because a caller that wants to read the
640
+ * SUBCOMMAND's flags (`warnGitClean`) needs to know where they start, and
641
+ * `args.indexOf("clean")` would land on a `-C clean` operand instead.
642
+ */
643
+ function gitSubcommandIndex(args: string[]): number {
628
644
  for (let i = 0; i < args.length; i++) {
629
645
  const arg = args[i];
630
646
  if (GIT_FLAGS_WITH_OPERANDS.has(arg)) {
@@ -632,9 +648,15 @@ function gitSubcommand(args: string[]): string | undefined {
632
648
  continue;
633
649
  }
634
650
  if (arg.startsWith("-")) continue;
635
- return arg;
651
+ return i;
636
652
  }
637
- return undefined;
653
+ return -1;
654
+ }
655
+
656
+ /** The `git` subcommand, with git's own global options walked off first. */
657
+ function gitSubcommand(args: string[]): string | undefined {
658
+ const i = gitSubcommandIndex(args);
659
+ return i < 0 ? undefined : args[i];
638
660
  }
639
661
 
640
662
  /**
@@ -1379,6 +1401,48 @@ const GIT_AMEND_RE = /\bgit\s+commit\b.*--amend\b/;
1379
1401
  // warnGitStashDrop
1380
1402
  const GIT_STASH_DROP_RE = /\bgit\s+stash\s+(?:drop|clear)\b/;
1381
1403
 
1404
+ // warnGitClean
1405
+ /**
1406
+ * `git`, however the command line spells it. Same shape as {@link RM_CMD_RE}:
1407
+ * an absolute path to the binary does not change what it does.
1408
+ */
1409
+ const GIT_CMD_RE = /^(?:\/\S*\/)?git$/;
1410
+
1411
+ /** A short-flag cluster: `-f`, `-fdx`, `-xdf`. */
1412
+ const SHORT_FLAG_CLUSTER_RE = /^-[A-Za-z]+$/;
1413
+
1414
+ /**
1415
+ * `clean.requireForce=false`, the config that makes `git clean` delete with no
1416
+ * `-f` at all — and settable per invocation as `git -c
1417
+ * clean.requireForce=false clean -xd`. Reading the subcommand's flags alone
1418
+ * would call that "no force, no warning" on the one spelling that needs none.
1419
+ */
1420
+ const GIT_CLEAN_FORCE_WAIVED_RE = /^clean\.requireforce=(?:false|0|no|off)$/i;
1421
+
1422
+ /**
1423
+ * The default `destructiveFlags`, and why the condition is these three letters
1424
+ * rather than "any `git clean`".
1425
+ *
1426
+ * `git clean` deletes nothing without `-f` / `--force` (or the config above),
1427
+ * and `-n` / `--dry-run` only prints — so force is the precondition and
1428
+ * dry-run is the exemption. What force ALONE removes is untracked files in the
1429
+ * current directory: the narrowest spelling, and the one people mean when they
1430
+ * tidy up. These are the letters that widen the blast radius past that:
1431
+ *
1432
+ * - `-x` also removes IGNORED files — `.env`, local config, editor state,
1433
+ * scratch work. That is the data that genuinely cannot be rebuilt by running
1434
+ * a command, and it is what makes `git clean -fdx` the severe spelling.
1435
+ * - `-X` removes ONLY ignored files: the same loss, tracked tree untouched.
1436
+ * - `-d` also removes untracked DIRECTORIES — whole new subtrees git has never
1437
+ * had a copy of.
1438
+ *
1439
+ * A bare `git clean -f` is deliberately absent. It has the smallest radius and
1440
+ * by far the most traffic, and a guard that fires on the ordinary spelling is
1441
+ * one people switch off — which would cost them the `-fdx` warning too. Add
1442
+ * `"f"` to `destructiveFlags` to opt into it.
1443
+ */
1444
+ const GIT_CLEAN_DESTRUCTIVE_FLAGS = ["d", "x", "X"];
1445
+
1382
1446
  // warnAllFilesStaged
1383
1447
  const GIT_ADD_ALL_RE = /\bgit\s+add\s+(?:-A\b|--all\b|\.(?:\s|$|;|&&|\|\|))/;
1384
1448
 
@@ -1710,6 +1774,9 @@ function protectEnvVars(ctx: PolicyContext): PolicyResult {
1710
1774
  if (EXPORT_RE.test(cmd)) {
1711
1775
  return deny("Command exports environment variable");
1712
1776
  }
1777
+ if (ENV_DUMP_RE.test(cmd) || (SCRIPT_INTERP_RE.test(cmd) && ENV_OBJECT_RE.test(cmd))) {
1778
+ return deny("Command dumps environment variables");
1779
+ }
1713
1780
  // PowerShell: $env:VAR
1714
1781
  if (PS_ENV_VAR_RE.test(cmd)) {
1715
1782
  return deny("Command reads environment variable via PowerShell");
@@ -2215,17 +2282,35 @@ const READ_LIKE_CMDS =
2215
2282
  * separator characters that appear in compound argv tokens (':' for Docker
2216
2283
  * volume mounts and PATH-like lists, '=' for env var assignments) so that a
2217
2284
  * suffix like '/dashboard.mdx' in 'docs/STAR/dashboard.mdx' or '/docs' in
2218
- * '-v HOST_DIR:/docs' is not misread as a standalone absolute path.
2285
+ * '-v HOST_DIR:/docs' is not misread as a standalone absolute path. '/' is in
2286
+ * that list too, so a match cannot START on the second slash of a protocol
2287
+ * separator: 'http://localhost:3000/x' used to yield '/localhost:3000/x'.
2288
+ * A match that starts on the FIRST slash is untouched, so '//etc/passwd' —
2289
+ * a real, working spelling of /etc/passwd — is still extracted and still
2290
+ * resolves to /etc/passwd.
2291
+ *
2292
+ * A run of slashes with nothing else ('//', '///') that opens a line after
2293
+ * the first is dropped: that is a line-comment marker in a heredoc body, not a
2294
+ * directory. It used to resolve to the filesystem root, which denied a heredoc
2295
+ * writing a TypeScript file into the project. Anywhere else a slash-run is
2296
+ * the root, as a lone '/' is: `cd // && cat etc/shadow` reads /etc/shadow.
2219
2297
  */
2220
2298
  function extractAbsolutePaths(command: string): string[] {
2221
2299
  const paths: string[] = [];
2222
- const pathRe = /(?<![a-zA-Z0-9_.\-~\\*?:=])(?:~\/[^\s;|&"'()\[\]{}]*|~(?=\s|$|[;|&"'()\[\]{}])|\/[^\s;|&"'()\[\]{}]*)/g;
2300
+ const pathRe = /(?<![a-zA-Z0-9_.\-~\\*?:=/])(?:~\/[^\s;|&"'()\[\]{}]*|~(?=\s|$|[;|&"'()\[\]{}])|\/[^\s;|&"'()\[\]{}]*)/g;
2223
2301
 
2224
2302
  function addPaths(s: string): void {
2225
2303
  pathRe.lastIndex = 0;
2226
2304
  let m: RegExpExecArray | null;
2227
2305
  while ((m = pathRe.exec(s)) !== null) {
2228
2306
  let p = m[0];
2307
+ // A `//` that opens a line after the first (a heredoc body) is a comment
2308
+ // marker. Anywhere else a slash-run is the root: `cd // && cat etc/shadow`,
2309
+ // and `cd \<newline>//`, which bash joins into one line.
2310
+ if (/^\/{2,}$/.test(p)) {
2311
+ const nl = s.lastIndexOf("\n", m.index - 1);
2312
+ if (nl !== -1 && s[nl - 1] !== "\\" && /^[ \t]*$/.test(s.slice(nl + 1, m.index))) continue;
2313
+ }
2229
2314
  if (p === "~") p = homedir();
2230
2315
  else if (p.startsWith("~/")) p = join(homedir(), p.slice(2));
2231
2316
  paths.push(p);
@@ -2516,6 +2601,101 @@ function warnGitStashDrop(ctx: PolicyContext): PolicyResult {
2516
2601
  return allow();
2517
2602
  }
2518
2603
 
2604
+ /** What one `git clean` argument list asks for. */
2605
+ interface GitCleanFlags {
2606
+ force: boolean;
2607
+ dryRun: boolean;
2608
+ /** Short-flag letters, with `--force` counted as `f` so widening works either way. */
2609
+ letters: Set<string>;
2610
+ }
2611
+
2612
+ /**
2613
+ * Read `git clean`'s own options: clusters (`-fdx`) and the two long forms
2614
+ * that matter (`--force`, `--dry-run`).
2615
+ *
2616
+ * Everything after `--` is a pathspec, not a flag — `git clean -n -- -fdx`
2617
+ * asks about a file literally called `-fdx` and deletes nothing.
2618
+ */
2619
+ function readGitCleanFlags(args: string[]): GitCleanFlags {
2620
+ const letters = new Set<string>();
2621
+ let force = false;
2622
+ let dryRun = false;
2623
+ let sawEndOfOptions = false;
2624
+ for (const token of args) {
2625
+ if (token === "--") {
2626
+ sawEndOfOptions = true;
2627
+ continue;
2628
+ }
2629
+ if (sawEndOfOptions) continue;
2630
+ if (token === "--force") {
2631
+ force = true;
2632
+ continue;
2633
+ }
2634
+ if (token === "--dry-run") {
2635
+ dryRun = true;
2636
+ continue;
2637
+ }
2638
+ if (token.startsWith("--")) continue;
2639
+ if (!SHORT_FLAG_CLUSTER_RE.test(token)) continue;
2640
+ for (const letter of token.slice(1)) {
2641
+ if (letter === "f") force = true;
2642
+ if (letter === "n") dryRun = true;
2643
+ letters.add(letter);
2644
+ }
2645
+ }
2646
+ if (force) letters.add("f");
2647
+ return { force, dryRun, letters };
2648
+ }
2649
+
2650
+ /** True when git's own `-c key=value` options waive `clean.requireForce`. */
2651
+ function gitCleanForceWaived(globalOptions: string[]): boolean {
2652
+ for (let i = 0; i < globalOptions.length; i++) {
2653
+ const token = globalOptions[i];
2654
+ const value =
2655
+ token === "-c" ? globalOptions[i + 1] : token.startsWith("-c") ? token.slice(2) : undefined;
2656
+ if (value !== undefined && GIT_CLEAN_FORCE_WAIVED_RE.test(value)) return true;
2657
+ }
2658
+ return false;
2659
+ }
2660
+
2661
+ /**
2662
+ * The gap this closes: `git clean -fdx` was caught by NEITHER tier — no regex
2663
+ * builtin matched the string at all, and the semantic check that covers
2664
+ * unrecoverable deletion cannot fire on it. See the note on the catalog entry
2665
+ * for the measurement and for why this policy is `hard`.
2666
+ */
2667
+ function warnGitClean(ctx: PolicyContext): PolicyResult {
2668
+ if (ctx.toolName !== "Bash") return allow();
2669
+ const destructiveFlags = ((ctx.params?.destructiveFlags ?? GIT_CLEAN_DESTRUCTIVE_FLAGS) as string[]);
2670
+ if (destructiveFlags.length === 0) return allow();
2671
+
2672
+ for (const segment of shellSegments(getCommand(ctx))) {
2673
+ const tokens = parseArgvTokens(segment);
2674
+ // `findIndex`, not `tokens[0]`, so an env prefix (`GIT_DIR=x git clean …`)
2675
+ // and an absolute binary path both still resolve to the git invocation.
2676
+ const gitIdx = tokens.findIndex((t) => GIT_CMD_RE.test(t));
2677
+ if (gitIdx < 0) continue;
2678
+ const args = tokens.slice(gitIdx + 1);
2679
+ const subIdx = gitSubcommandIndex(args);
2680
+ if (subIdx < 0 || args[subIdx] !== "clean") continue;
2681
+
2682
+ const flags = readGitCleanFlags(args.slice(subIdx + 1));
2683
+ // `-n` / `--dry-run` prints what would go and removes nothing, whatever
2684
+ // else is on the line — the safe inspection form, never a warning.
2685
+ if (flags.dryRun) continue;
2686
+ if (!flags.force && !gitCleanForceWaived(args.slice(0, subIdx))) continue;
2687
+ if (!destructiveFlags.some((f) => flags.letters.has(f))) continue;
2688
+
2689
+ return instruct(
2690
+ "STOP: This command deletes untracked files from the working tree (git clean). Git has no copy of " +
2691
+ "untracked or ignored files, so nothing it removes can be recovered: -d takes whole untracked " +
2692
+ "directories, and -x / -X also take .gitignore'd files such as .env, local config and scratch work. " +
2693
+ "Re-run it with --dry-run and confirm the exact paths with the user before executing.",
2694
+ );
2695
+ }
2696
+ return allow();
2697
+ }
2698
+
2519
2699
  function warnAllFilesStaged(ctx: PolicyContext): PolicyResult {
2520
2700
  if (ctx.toolName !== "Bash") return allow();
2521
2701
  const cmd = getCommand(ctx);
@@ -3078,7 +3258,7 @@ function requireCiGreenBeforeStop(ctx: PolicyContext): PolicyResult {
3078
3258
  * Each value is the identical hoisted function object, never a wrapper. Two
3079
3259
  * things depend on that and neither fails loudly: `audit/cache.ts` hashes
3080
3260
  * `fn.toString()` into the audit cache's `engineVersion`, so wrapping every
3081
- * entry would collapse 39 distinct hashes into one and freeze the key — stale
3261
+ * entry would collapse 40 distinct hashes into one and freeze the key — stale
3082
3262
  * audit results would then be served for the full 30-day TTL with no symptom;
3083
3263
  * and `gitBranchCache` is module-scoped, so a per-call factory would silently
3084
3264
  * reset it on every hook event.
@@ -3109,6 +3289,7 @@ const POLICY_IMPLEMENTATIONS: Record<string, PolicyFunction> = {
3109
3289
  "block-work-on-main": blockWorkOnMain,
3110
3290
  "warn-git-amend": warnGitAmend,
3111
3291
  "warn-git-stash-drop": warnGitStashDrop,
3292
+ "warn-git-clean": warnGitClean,
3112
3293
  "warn-all-files-staged": warnAllFilesStaged,
3113
3294
  "warn-destructive-sql": warnDestructiveSql,
3114
3295
  "warn-schema-alteration": warnSchemaAlteration,
@@ -3178,7 +3359,13 @@ export function registerBuiltinPolicies(enabledNames: string[]): void {
3178
3359
  // A guard against the agent disabling failproofai that any of those can
3179
3360
  // switch off is not a guard.
3180
3361
  if (policy.alwaysOn || enabledSet.has(normalizePolicyName(policy.name))) {
3181
- registerPolicy(policy.name, policy.description, policy.fn, policy.match, 0, policy.params);
3362
+ // `alwaysOn` travels with the declaration so the self-protection guard
3363
+ // resolves to hard even if its catalog entry ever said otherwise.
3364
+ registerPolicy(policy.name, policy.description, policy.fn, policy.match, 0, policy.params, {
3365
+ authority: policy.authority,
3366
+ reviewedBy: policy.reviewedBy,
3367
+ alwaysOn: policy.alwaysOn,
3368
+ });
3182
3369
  }
3183
3370
  }
3184
3371
  }