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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  3. package/.next/standalone/.next/prerender-manifest.json +5 -5
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/invite/route_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
  26. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/run/route_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/status/route_client-reference-manifest.js +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/login-request/route_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-verify/route_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  42. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +37 -4
  43. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  44. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/index.html +1 -1
  47. package/.next/standalone/.next/server/app/index.rsc +15 -15
  48. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  49. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  50. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  51. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  52. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  53. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  57. package/.next/standalone/.next/server/app/policies/page.js +1 -1
  58. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
  63. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  66. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  67. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  68. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  73. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  74. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  77. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  78. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/chunks/{[externals]__1zndo1i._.js → [externals]__1lh7m5d._.js} +1 -1
  81. package/.next/standalone/.next/server/chunks/{[externals]__0_gzk8q._.js → [externals]__1rqkg_y._.js} +1 -1
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +3 -0
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
  88. package/.next/standalone/.next/server/chunks/_08w6xzm._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +14 -21
  93. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  95. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  96. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00-s7h8._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
  104. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0wvusu0._.js → [root-of-the-server]__0n_lxhg._.js} +2 -2
  106. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rgu2r3._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__190won8._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1sc84bb._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zl_r0t._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/_0-oij9d._.js +23 -0
  117. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/_0oq1dh6._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/_0wso5d3._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +48 -168
  123. package/.next/standalone/.next/server/chunks/ssr/{_08f0jd2._.js → _1if9ixi._.js} +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  126. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -69
  127. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
  133. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  135. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  136. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  137. package/.next/standalone/.next/server/pages/404.html +1 -1
  138. package/.next/standalone/.next/server/pages/500.html +1 -1
  139. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  140. package/.next/standalone/.next/server/server-reference-manifest.json +54 -21
  141. package/.next/standalone/.next/static/chunks/{1_bxotmf9qngs.js → 010bv1w6j171t.js} +1 -1
  142. package/.next/standalone/.next/static/chunks/04r6ch8uf_n8m.js +1 -0
  143. package/.next/standalone/.next/static/chunks/{29-iqnp9v_axt.css → 0o-hh5_turzlz.css} +1 -1
  144. package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +2 -0
  145. package/.next/standalone/.next/static/chunks/{00gv0v91mycm-.js → 0wz8yftk18ts2.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/0zebh1n9jkfbt.js +1 -0
  147. package/.next/standalone/.next/static/chunks/{0qn9khv0oo9ex.js → 1pb1oztsbwcss.js} +1 -1
  148. package/.next/standalone/.next/static/chunks/2aquitk72k2op.js +1 -0
  149. package/.next/standalone/.next/static/chunks/2bi_1y0a_smt7.js +6 -0
  150. package/.next/standalone/.next/static/chunks/{1po3lirgk2riw.js → 2ej3b8gk5ittu.js} +1 -1
  151. package/.next/standalone/.next/static/chunks/{01hvwwrhvbzyh.js → 2zafgs90r_leq.js} +1 -1
  152. package/.next/standalone/.next/static/chunks/{1_2jsatfuthrw.js → 32spub4wqjem-.js} +1 -1
  153. package/.next/standalone/.next/static/chunks/{3_--3ibia6ba1.js → 3l36d73kj18zw.js} +1 -1
  154. package/.next/standalone/.next/static/chunks/{2bvwsyu6x__ia.js → 3m4upvybtrexd.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{2k9f4tyv04809.css → 3uyhmm01g64k4.css} +1 -0
  156. package/.next/standalone/.next/static/chunks/{2bnf_4pgyk1rq.js → 40lpt2o2b283e.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{turbopack-18qf4jodtft6b.js → turbopack-3s3c-u_u_l0u9.js} +1 -1
  158. package/.next/standalone/.opencode/plugins/failproofai.mjs +51 -27
  159. package/.next/standalone/app/actions/get-leaks.ts +100 -0
  160. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +112 -33
  161. package/.next/standalone/app/audit/_components/audit-poster.tsx +29 -10
  162. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +6 -1
  163. package/.next/standalone/app/audit/_components/empty-state.tsx +4 -1
  164. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +19 -5
  165. package/.next/standalone/app/audit/_components/leak-section.tsx +179 -0
  166. package/.next/standalone/app/audit/_components/share-templates.ts +110 -44
  167. package/.next/standalone/app/audit/audit-styles.css +114 -1
  168. package/.next/standalone/app/project/[name]/page.tsx +4 -24
  169. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +26 -3
  170. package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +31 -8
  171. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +1 -0
  172. package/.next/standalone/fp-cloud-cli/pyproject.toml +9 -2
  173. package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +54 -0
  174. package/.next/standalone/fp-cloud-cli/uv.lock +9 -12
  175. package/.next/standalone/lib/auth/api-server-client.ts +28 -0
  176. package/.next/standalone/lib/claude-sessions.ts +101 -27
  177. package/.next/standalone/lib/cli-registry.ts +1 -33
  178. package/.next/standalone/lib/download-session.ts +0 -37
  179. package/.next/standalone/lib/projects.ts +3 -27
  180. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  181. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  182. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
  183. package/.next/standalone/node_modules/next/dist/experimental/testmode/fetch.js +10 -2
  184. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  185. package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeScriptCli.js +2 -2
  186. package/.next/standalone/node_modules/next/dist/lib/verify-typescript-setup.js +3 -1
  187. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  188. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  189. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  190. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +2 -2
  191. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  192. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  193. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  194. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  195. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  196. package/.next/standalone/node_modules/next/package.json +11 -11
  197. package/.next/standalone/package.json +15 -14
  198. package/.next/standalone/sdk/python/CHANGELOG.md +155 -0
  199. package/.next/standalone/sdk/python/README.md +8 -0
  200. package/.next/standalone/sdk/python/examples/evaluator_worker.py +121 -0
  201. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  202. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +101 -0
  203. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__main__.py +49 -0
  204. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/_sandbox_runner.py +63 -0
  205. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +404 -0
  206. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/client.py +299 -0
  207. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/protocol.py +754 -0
  208. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +936 -0
  209. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/source.py +684 -0
  210. package/.next/standalone/sdk/python/tests/fixtures/evaluator_v2/README.md +28 -0
  211. package/.next/standalone/sdk/python/tests/fixtures/evaluator_v2/contract.json +252 -0
  212. package/.next/standalone/sdk/python/tests/test_evaluator_authoring.py +124 -0
  213. package/.next/standalone/sdk/python/tests/test_evaluator_client.py +253 -0
  214. package/.next/standalone/sdk/python/tests/test_evaluator_example.py +35 -0
  215. package/.next/standalone/sdk/python/tests/test_evaluator_http_e2e.py +636 -0
  216. package/.next/standalone/sdk/python/tests/test_evaluator_main.py +47 -0
  217. package/.next/standalone/sdk/python/tests/test_evaluator_protocol.py +246 -0
  218. package/.next/standalone/sdk/python/tests/test_evaluator_review_fixes.py +164 -0
  219. package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +1103 -0
  220. package/.next/standalone/sdk/python/tests/test_evaluator_source.py +430 -0
  221. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +17 -0
  222. package/.next/standalone/sdk/python/uv.lock +18 -18
  223. package/.next/standalone/server.js +1 -1
  224. package/README.md +19 -95
  225. package/bin/failproofai.mjs +7 -24
  226. package/dist/cli.mjs +19910 -25855
  227. package/dist/worker.mjs +4818 -6344
  228. package/lib/auth/api-server-client.ts +28 -0
  229. package/lib/claude-sessions.ts +101 -27
  230. package/lib/cli-registry.ts +1 -33
  231. package/lib/download-session.ts +0 -37
  232. package/lib/projects.ts +3 -27
  233. package/package.json +15 -14
  234. package/pi-extension/index.ts +42 -4
  235. package/scripts/changelog-open.py +115 -0
  236. package/src/audit/cli-adapters/index.ts +0 -24
  237. package/src/audit/cli.ts +134 -0
  238. package/src/audit/desktop-notify.ts +420 -0
  239. package/src/audit/harm-report.ts +101 -0
  240. package/src/audit/index.ts +141 -13
  241. package/src/audit/leak-fingerprint.ts +200 -0
  242. package/src/audit/leak-notice.ts +161 -0
  243. package/src/audit/leak-record.ts +232 -0
  244. package/src/audit/leak-scan.ts +292 -0
  245. package/src/audit/leak-store.ts +217 -0
  246. package/src/audit/macos-notifier.ts +310 -0
  247. package/src/audit/redact-example.ts +266 -14
  248. package/src/audit/report-harm.ts +12 -1
  249. package/src/audit/report.ts +6 -3
  250. package/src/audit/schedule-cli.ts +37 -0
  251. package/src/audit/scoring.ts +49 -0
  252. package/src/audit/types.ts +41 -0
  253. package/src/hooks/builtin-policies.ts +71 -8
  254. package/src/hooks/configure-wizard.ts +20 -0
  255. package/src/hooks/enforcement-capability.ts +0 -97
  256. package/src/hooks/fp-config.ts +71 -11
  257. package/src/hooks/fp-home.ts +27 -0
  258. package/src/hooks/handler.ts +64 -62
  259. package/src/hooks/harness-cli.ts +1 -3
  260. package/src/hooks/integrations.ts +49 -769
  261. package/src/hooks/normalize-cli-payload.ts +0 -126
  262. package/src/hooks/notice.ts +155 -0
  263. package/src/hooks/policy-evaluator.ts +6 -297
  264. package/src/hooks/resolve-transcript-path.ts +0 -10
  265. package/src/hooks/tool-name-canonicalize.ts +0 -64
  266. package/src/hooks/types.ts +2 -533
  267. package/src/hooks/uninstall-cli.ts +15 -0
  268. package/.next/standalone/.grok/hooks/failproofai.json +0 -172
  269. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07hor7h._.js +0 -5
  270. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0elsuli._.js +0 -3
  271. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0gnj_qa._.js +0 -3
  272. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljuzm_._.js +0 -3
  273. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w23bpg._.js +0 -3
  274. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0x5c7sh._.js +0 -3
  275. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yik9_w._.js +0 -3
  276. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1jg6s9m._.js +0 -3
  277. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1s6qci9._.js +0 -3
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xfm05z._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/_0tdy_6l._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/lib_grok-projects_ts_1i2epl_._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/lib_qwen-projects_ts_0wu_a8e._.js +0 -3
  282. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_4gcz8._.js +0 -3
  283. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0d--wmy._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qqrlf_._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s11fx1._.js +0 -3
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gamwq._.js +0 -3
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bbk-si._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1c3ybxr._.js +0 -3
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1izi7zn._.js +0 -3
  290. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__21bj7uw._.js +0 -3
  291. package/.next/standalone/.next/server/chunks/ssr/_00z5uuq._.js +0 -3
  292. package/.next/standalone/.next/server/chunks/ssr/_1byck5v._.js +0 -23
  293. package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +0 -3
  294. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +0 -3
  295. package/.next/standalone/.next/server/chunks/ssr/lib_cline-projects_ts_13_hbdd._.js +0 -3
  296. package/.next/standalone/.next/server/chunks/ssr/lib_grok-projects_ts_0rc-d0o._.js +0 -3
  297. package/.next/standalone/.next/server/chunks/ssr/lib_ori-projects_ts_04tofqo._.js +0 -3
  298. package/.next/standalone/.next/server/chunks/ssr/lib_qwen-projects_ts_0w3i9o7._.js +0 -3
  299. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +0 -3
  300. package/.next/standalone/.next/static/chunks/0hzmxwb2pvk5p.js +0 -6
  301. package/.next/standalone/.next/static/chunks/0j884dwnwtyrt.js +0 -1
  302. package/.next/standalone/.next/static/chunks/0mr9dg3bnloa1.js +0 -1
  303. package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +0 -1
  304. package/.next/standalone/.next/static/chunks/3gf1jg-5m-u1t.css +0 -2
  305. package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +0 -1
  306. package/.next/standalone/.next/static/chunks/3ws7eljkq25wg.js +0 -69
  307. package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +0 -1
  308. package/.next/standalone/.qwen/settings.json +0 -251
  309. package/.next/standalone/assets/logos/cline.png +0 -0
  310. package/.next/standalone/assets/logos/grok-dark.svg +0 -1
  311. package/.next/standalone/assets/logos/grok-light.svg +0 -1
  312. package/.next/standalone/assets/logos/ori-dark.svg +0 -1
  313. package/.next/standalone/assets/logos/ori-light.svg +0 -1
  314. package/.next/standalone/assets/logos/qwen.svg +0 -1
  315. package/.next/standalone/lib/cline-projects.ts +0 -155
  316. package/.next/standalone/lib/cline-sessions.ts +0 -268
  317. package/.next/standalone/lib/grok-projects.ts +0 -123
  318. package/.next/standalone/lib/grok-sessions.ts +0 -355
  319. package/.next/standalone/lib/ori-projects.ts +0 -141
  320. package/.next/standalone/lib/ori-sessions.ts +0 -221
  321. package/.next/standalone/lib/qwen-projects.ts +0 -106
  322. package/.next/standalone/lib/qwen-sessions.ts +0 -313
  323. package/lib/cline-projects.ts +0 -155
  324. package/lib/cline-sessions.ts +0 -268
  325. package/lib/grok-projects.ts +0 -123
  326. package/lib/grok-sessions.ts +0 -355
  327. package/lib/ori-projects.ts +0 -141
  328. package/lib/ori-sessions.ts +0 -221
  329. package/lib/qwen-projects.ts +0 -106
  330. package/lib/qwen-sessions.ts +0 -313
  331. package/src/audit/cli-adapters/cline.ts +0 -50
  332. package/src/audit/cli-adapters/grok.ts +0 -70
  333. package/src/audit/cli-adapters/ori.ts +0 -55
  334. package/src/audit/cli-adapters/qwen.ts +0 -71
  335. package/src/hooks/batch-expand.ts +0 -380
  336. package/src/hooks/batch-fanout.ts +0 -142
  337. package/src/hooks/risk-patterns.ts +0 -26
  338. /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → aKNv4Kmv98Xpwns0Xa-C-}/_buildManifest.js +0 -0
  339. /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → aKNv4Kmv98Xpwns0Xa-C-}/_clientMiddlewareManifest.js +0 -0
  340. /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → aKNv4Kmv98Xpwns0Xa-C-}/_ssgManifest.js +0 -0
@@ -1,70 +0,0 @@
1
- /**
2
- * grok CLI transcript adapter — AUDIT (Pillar 2).
3
- *
4
- * grok writes one session DIRECTORY at
5
- * `~/.grok/sessions/<percent-encoded-cwd>/<sessionId>/`, whose
6
- * `chat_history.jsonl` holds the turns (verified live against grok 1.0.3).
7
- * lib/grok-sessions.ts enumerates and parses them.
8
- *
9
- * `chat_history.jsonl` has no per-message timestamps, so the parser anchors a
10
- * synthesized timeline on the session's `created_at` (from summary.json) —
11
- * `listGrokTranscripts` already resolved that, so it is passed straight
12
- * through here rather than re-read.
13
- */
14
- import { readFile } from "node:fs/promises";
15
- import { listGrokTranscripts, grokLinesToLogEntries } from "../../../lib/grok-sessions";
16
- import { parseRawLines } from "../../../lib/log-entries";
17
- import type { NormalizedToolEvent, TranscriptMetadata } from "../types";
18
- import type { ListOpts } from "./claude";
19
- import { logEntriesToEvents } from "./shared";
20
-
21
- export async function listGrokTranscriptMetadata(
22
- opts: ListOpts = {},
23
- ): Promise<TranscriptMetadata[]> {
24
- const projectFilter = opts.projects ? new Set(opts.projects) : null;
25
- const sinceMs = opts.sinceMs ?? 0;
26
- const out: TranscriptMetadata[] = [];
27
-
28
- for (const t of listGrokTranscripts()) {
29
- if (t.mtimeMs < sinceMs) continue;
30
- // `audit --project <cwd>` filters on the real cwd (summary.json's info.cwd
31
- // when present, else the percent-decoded folder name).
32
- if (projectFilter && !projectFilter.has(t.cwd)) continue;
33
- out.push({
34
- cli: "grok",
35
- projectName: t.projectName,
36
- sessionId: t.sessionId,
37
- transcriptPath: t.transcriptPath,
38
- mtimeMs: t.mtimeMs,
39
- sizeBytes: t.sizeBytes,
40
- });
41
- }
42
- return out;
43
- }
44
-
45
- export async function streamGrokEvents(meta: TranscriptMetadata): Promise<NormalizedToolEvent[]> {
46
- let content: string;
47
- try {
48
- content = await readFile(meta.transcriptPath, "utf-8");
49
- } catch {
50
- return [];
51
- }
52
-
53
- // Recover this session's cwd + created_at from the enumeration (both come
54
- // from summary.json, which streamEvents is not otherwise given).
55
- const known = listGrokTranscripts().find((t) => t.sessionId === meta.sessionId);
56
-
57
- const rawLines = parseRawLines(content, "session");
58
- const entries = grokLinesToLogEntries(
59
- rawLines,
60
- known?.createdAtMs ?? meta.mtimeMs,
61
- "session",
62
- );
63
-
64
- return logEntriesToEvents(entries, {
65
- cli: "grok",
66
- sessionId: meta.sessionId,
67
- transcriptPath: meta.transcriptPath,
68
- cwd: known?.cwd ?? "",
69
- });
70
- }
@@ -1,55 +0,0 @@
1
- /**
2
- * Ori (OpenRouter's ori) transcript adapter — AUDIT-ONLY (Pillar 2).
3
- *
4
- * Covers ori's OWN agent (bare `ori` / `ori code`). Sessions launched through
5
- * ori's LAUNCHER (`ori claude`, `ori codex`, …) are recorded by the launched
6
- * CLI in its own store and are picked up by that CLI's adapter, not this one.
7
- *
8
- * ori keeps the whole conversation in one SQLite DB in its global workspace —
9
- * lib/ori-projects.ts enumerates `ori_agent_loop_sessions`, lib/ori-sessions.ts
10
- * parses each session's serialized message array — producing the same
11
- * LogEntry[] shape the other adapters do, so `logEntriesToEvents` handles the
12
- * rest. Each session carries a real `cwd`, so `audit --project <cwd>` filters
13
- * work as they do for Devin and Goose.
14
- */
15
- import { getOriSessions } from "../../../lib/ori-projects";
16
- import { getOriSessionLog } from "../../../lib/ori-sessions";
17
- import type { NormalizedToolEvent, TranscriptMetadata } from "../types";
18
- import type { ListOpts } from "./claude";
19
- import { logEntriesToEvents } from "./shared";
20
-
21
- export async function listOriTranscriptMetadata(
22
- opts: ListOpts = {},
23
- ): Promise<TranscriptMetadata[]> {
24
- const projectFilter = opts.projects ? new Set(opts.projects) : null;
25
- const sinceMs = opts.sinceMs ?? 0;
26
- const sessions = await getOriSessions();
27
- const out: TranscriptMetadata[] = [];
28
- for (const s of sessions) {
29
- if (s.mtimeMs < sinceMs) continue;
30
- if (projectFilter && (!s.cwd || !projectFilter.has(s.cwd))) continue;
31
- out.push({
32
- cli: "ori",
33
- projectName: s.projectName,
34
- sessionId: s.sessionId,
35
- transcriptPath: `ori-db://${s.sessionId}`,
36
- mtimeMs: s.mtimeMs,
37
- // updated_at advances on each turn, so (mtime) forms a real cache key.
38
- sizeBytes: 0,
39
- });
40
- }
41
- return out;
42
- }
43
-
44
- export async function streamOriEvents(
45
- meta: TranscriptMetadata,
46
- ): Promise<NormalizedToolEvent[]> {
47
- const log = await getOriSessionLog(meta.sessionId);
48
- if (!log) return [];
49
- return logEntriesToEvents(log.entries, {
50
- cli: "ori",
51
- sessionId: meta.sessionId,
52
- transcriptPath: meta.transcriptPath,
53
- cwd: log.cwd ?? "",
54
- });
55
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * Qwen Code transcript adapter — AUDIT (Pillar 2).
3
- *
4
- * qwen writes real JSONL transcripts at
5
- * `~/.qwen/projects/<encoded-cwd>/chats/<sessionId>.jsonl` (Claude-style
6
- * encoded-cwd folders, one level deeper than Claude's; verified live against
7
- * qwen-code 0.21.12). lib/qwen-sessions.ts enumerates and parses them — the
8
- * message bodies are Gemini-shaped `parts[]`, not Claude content blocks — into
9
- * the shared LogEntry[] shape, so `logEntriesToEvents` handles the rest.
10
- */
11
- import { readFile } from "node:fs/promises";
12
- import { listQwenTranscripts, qwenLinesToLogEntries } from "../../../lib/qwen-sessions";
13
- import { parseRawLines } from "../../../lib/log-entries";
14
- import type { NormalizedToolEvent, TranscriptMetadata } from "../types";
15
- import type { ListOpts } from "./claude";
16
- import { logEntriesToEvents } from "./shared";
17
-
18
- export async function listQwenTranscriptMetadata(
19
- opts: ListOpts = {},
20
- ): Promise<TranscriptMetadata[]> {
21
- const projectFilter = opts.projects ? new Set(opts.projects) : null;
22
- const sinceMs = opts.sinceMs ?? 0;
23
- const out: TranscriptMetadata[] = [];
24
-
25
- for (const t of listQwenTranscripts()) {
26
- if (t.mtimeMs < sinceMs) continue;
27
- // `audit --project <cwd>` filters on the decoded working directory.
28
- if (projectFilter && !projectFilter.has(t.cwd)) continue;
29
- out.push({
30
- cli: "qwen",
31
- projectName: t.projectName,
32
- sessionId: t.sessionId,
33
- transcriptPath: t.transcriptPath,
34
- mtimeMs: t.mtimeMs,
35
- sizeBytes: t.sizeBytes,
36
- });
37
- }
38
- return out;
39
- }
40
-
41
- export async function streamQwenEvents(meta: TranscriptMetadata): Promise<NormalizedToolEvent[]> {
42
- let content: string;
43
- try {
44
- content = await readFile(meta.transcriptPath, "utf-8");
45
- } catch {
46
- return [];
47
- }
48
-
49
- const rawLines = parseRawLines(content, "session");
50
- const entries = qwenLinesToLogEntries(rawLines, "session");
51
-
52
- // Every qwen line carries the real cwd — take the first rather than
53
- // re-decoding the (lossy) folder name.
54
- let cwd = "";
55
- for (const line of rawLines) {
56
- if (line && typeof line === "object") {
57
- const c = (line as Record<string, unknown>).cwd;
58
- if (typeof c === "string" && c.length > 0) {
59
- cwd = c;
60
- break;
61
- }
62
- }
63
- }
64
-
65
- return logEntriesToEvents(entries, {
66
- cli: "qwen",
67
- sessionId: meta.sessionId,
68
- transcriptPath: meta.transcriptPath,
69
- cwd,
70
- });
71
- }
@@ -1,380 +0,0 @@
1
- /**
2
- * Expand a CLI's BATCH tool call into per-element canonical scalar inputs.
3
- *
4
- * failproofai's builtins read SCALARS — `tool_input.command`, `.file_path`,
5
- * `.pattern`, `.old_string`. Cline delivers ARRAYS: `run_commands{commands[]}`,
6
- * `read_files{files[]}`, `search_codebase{queries[]}`, plus one multi-file blob
7
- * in `apply_patch{input}`. With only a key rename, `block-sudo`,
8
- * `block-env-files`, `block-secrets-write`, `block-force-push` and
9
- * `block-read-outside-cwd` all read `undefined` and ALLOW SILENTLY — the
10
- * inert-hook failure this repo has already shipped twice (grok, and our own
11
- * Claude hooks running inside grok).
12
- *
13
- * Joining the array into one string is NOT a fix, and the reason is specific:
14
- * `SECRET_FILE_RE` is `/\.(?:pem|key)$/`. Under any join only the LAST element
15
- * can ever match, so a `.pem` at `files[0]` rides straight through. That is why
16
- * this expands and the evaluator runs the policy set once per element.
17
- *
18
- * PURE: no evaluator import, no fs, no policy registry — so the audit path can
19
- * call it without dragging the policy engine along.
20
- *
21
- * CONTAINMENT: returns null for every CLI except cline and every tool except
22
- * cline's four containers. `null` means "take the existing single-shot path,
23
- * byte-for-byte unchanged", so this cannot regress the other 15 integrations.
24
- */
25
- import type { IntegrationType } from "./types";
26
- import {
27
- ENV_FILE_PATH_RE,
28
- SECRET_FILE_RE,
29
- SECRET_FILE_ID_RSA_RE,
30
- SECRET_FILE_CREDENTIALS_RE,
31
- } from "./risk-patterns";
32
-
33
- export interface FanoutElement {
34
- /** Canonical SCALAR tool input: {command} | {file_path,…} | {pattern} |
35
- * {file_path, old_string, new_string, patch}. */
36
- input: Record<string, unknown>;
37
- /** Short human locator; goes into the deny message and the activity row. */
38
- label: string;
39
- index: number;
40
- /** True when this element is a COLLAPSE of several that the cap or the
41
- * wall-clock budget refused to run apart. A weaker guarantee we state
42
- * explicitly, never a silent skip. */
43
- degraded?: boolean;
44
- }
45
-
46
- export interface BatchExpansion {
47
- /** Canonical tool name (Bash | Read | Grep | Edit). */
48
- tool: string;
49
- elements: FanoutElement[];
50
- /** The un-expanded original, so a batch-aware policy still sees the whole call. */
51
- raw: Record<string, unknown>;
52
- }
53
-
54
- /**
55
- * The separator used wherever a command list still has to collapse to one
56
- * string. `" &&\n"` is DERIVED, not chosen:
57
- * • `"\n"` alone silently disables `READ_LIKE_CMDS` — its boundary
58
- * alternation `(?:^|;|&&|\|\||\|)` contains no newline, so a batched
59
- * `cat /etc/passwd` stops looking read-like and `block-read-outside-cwd`
60
- * never inspects it.
61
- * • `" && "` alone manufactures a FALSE deny: a `.`-based lookahead can cross
62
- * the boundary and fire on two commands neither of which triggers it alone.
63
- * `" &&\n"` keeps every `&&`-aware segmenter alive while the newline stops
64
- * `.`-based lookaheads crossing a boundary.
65
- */
66
- export const BATCH_JOIN = " &&\n";
67
-
68
- function omit(o: Record<string, unknown>, key: string): Record<string, unknown> {
69
- const { [key]: _dropped, ...rest } = o;
70
- return rest;
71
- }
72
-
73
- export function expandBatchToolInput(
74
- cli: IntegrationType,
75
- toolName: string | undefined,
76
- rawInput: unknown,
77
- ): BatchExpansion | null {
78
- if (cli !== "cline" || !toolName) return null;
79
- if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) return null;
80
- const raw = rawInput as Record<string, unknown>;
81
-
82
- // An expansion to ZERO elements would evaluate nothing and report a clean
83
- // allow — the exact inert-hook failure this exists to prevent. So an empty or
84
- // mis-shaped container returns null and falls back to the collapse safety net
85
- // in canonicalizeClineToolInput, which at least still reads SOMETHING.
86
- const mk = (
87
- parts: Array<{ input: Record<string, unknown>; label: string }>,
88
- ): BatchExpansion | null =>
89
- parts.length === 0
90
- ? null
91
- : { tool: toolName, raw, elements: parts.map((e, index) => ({ ...e, index })) };
92
-
93
- switch (toolName) {
94
- // run_commands {commands: ["cd '<dir>' && ls -la", …]}
95
- case "Bash": {
96
- const cmds = raw.commands;
97
- if (!Array.isArray(cmds)) return null;
98
- return mk(
99
- cmds
100
- .filter((c): c is string => typeof c === "string")
101
- .map((c) => ({ input: { ...omit(raw, "commands"), command: c }, label: c })),
102
- );
103
- }
104
- // read_files {files: [{path, start_line, …}, …]}
105
- case "Read": {
106
- const files = raw.files;
107
- if (!Array.isArray(files)) return null;
108
- return mk(
109
- files.flatMap((f) => {
110
- if (!f || typeof f !== "object" || Array.isArray(f)) return [];
111
- const e = f as Record<string, unknown>;
112
- if (typeof e.path !== "string") return [];
113
- // Spread the entry so start_line/limit survive for policies that read
114
- // them; `path` is KEPT too, because a cline-aware custom policy may
115
- // already be reading it.
116
- return [{ input: { ...e, file_path: e.path }, label: e.path }];
117
- }),
118
- );
119
- }
120
- // search_codebase {queries: ["alpha", …]}
121
- case "Grep": {
122
- const qs = raw.queries;
123
- if (!Array.isArray(qs)) return null;
124
- return mk(
125
- qs
126
- .filter((q): q is string => typeof q === "string")
127
- .map((q) => ({ input: { ...omit(raw, "queries"), pattern: q }, label: q })),
128
- );
129
- }
130
- // apply_patch {input: "*** Begin Patch …"} — byte-identical to the OpenAI
131
- // apply_patch blob ori's `edit` tool carries, so this splitter is
132
- // back-portable to ori and closes ori's documented multi-file KNOWN GAP.
133
- case "Edit": {
134
- const blob = raw.input;
135
- if (typeof blob !== "string") return null;
136
- return mk(
137
- splitApplyPatch(blob).map((s) => ({
138
- input: {
139
- file_path: s.path,
140
- old_string: s.oldText,
141
- new_string: s.newText,
142
- patch: s.subPatch,
143
- cline_patch_op: s.op,
144
- },
145
- label: `${s.op} ${s.path}`,
146
- })),
147
- );
148
- }
149
- default:
150
- return null;
151
- }
152
- }
153
-
154
- export interface PatchSection {
155
- op: "add" | "update" | "delete" | "move";
156
- path: string;
157
- oldText: string;
158
- newText: string;
159
- /** A standalone, re-emittable patch for just this file. */
160
- subPatch: string;
161
- }
162
-
163
- const FILE_OP_RE = /^\*\*\* (Add File|Update File|Delete File|Move to): (.+)$/;
164
- const OPS: Record<string, PatchSection["op"]> = {
165
- "Add File": "add",
166
- "Update File": "update",
167
- "Delete File": "delete",
168
- "Move to": "move",
169
- };
170
- const HEADERS: Record<PatchSection["op"], string> = {
171
- add: "Add File",
172
- update: "Update File",
173
- delete: "Delete File",
174
- move: "Move to",
175
- };
176
-
177
- interface OpenSection {
178
- op: PatchSection["op"];
179
- path: string;
180
- body: string[];
181
- }
182
-
183
- function build(cur: OpenSection, op: PatchSection["op"], path: string): PatchSection {
184
- const minus: string[] = [];
185
- const plus: string[] = [];
186
- for (const l of cur.body) {
187
- if (l.startsWith("-")) minus.push(l.slice(1));
188
- else if (l.startsWith("+")) plus.push(l.slice(1));
189
- }
190
- return {
191
- op,
192
- path,
193
- oldText: minus.join("\n"),
194
- newText: plus.join("\n"),
195
- subPatch: ["*** Begin Patch", `*** ${HEADERS[op]}: ${path}`, ...cur.body, "*** End Patch"].join("\n"),
196
- };
197
- }
198
-
199
- /**
200
- * Split an OpenAI apply_patch blob into one section per file it touches.
201
- *
202
- * Serves BOTH cline's `apply_patch` and ori's `edit` — the format is identical,
203
- * and one implementation is what stops a third CLI adding a third regex.
204
- */
205
- export function splitApplyPatch(patch: string): PatchSection[] {
206
- const out: PatchSection[] = [];
207
- let cur: OpenSection | null = null;
208
-
209
- for (const line of patch.split("\n")) {
210
- const m = FILE_OP_RE.exec(line);
211
- if (m) {
212
- const op = OPS[m[1]!]!;
213
- const path = m[2]!.trim();
214
- if (op === "move") {
215
- // `Move to:` is a RENAME MODIFIER on the preceding Update File, not an
216
- // independent op. We emit it as its OWN element anyway — deliberately —
217
- // so a rename INTO `.env` is seen by the path builtins. It inherits the
218
- // in-progress body so old/new_string stay right, and the Update element
219
- // is still emitted at the next flush. Cost: one rename yields two
220
- // elements. That is the trade: a duplicate row, never a missed path.
221
- // Flush the section being renamed FIRST, then emit the move, so paths
222
- // come out in literal file order — `Update File: a` + `Move to: b`
223
- // yields [a, b], never [b, a]. Getting this backwards silently changes
224
- // which path `pickRiskiestPath` and `file_path = paths[0]` land on.
225
- if (cur) {
226
- out.push(build(cur, cur.op, cur.path));
227
- out.push(build(cur, "move", path));
228
- cur = null;
229
- } else
230
- out.push({
231
- op: "move",
232
- path,
233
- oldText: "",
234
- newText: "",
235
- subPatch: `*** Begin Patch\n*** Move to: ${path}\n*** End Patch`,
236
- });
237
- continue;
238
- }
239
- if (cur) out.push(build(cur, cur.op, cur.path));
240
- cur = { op, path, body: [] };
241
- continue;
242
- }
243
- if (line.startsWith("*** ")) continue; // Begin Patch / End Patch / End of File
244
- if (cur) cur.body.push(line);
245
- }
246
- if (cur) out.push(build(cur, cur.op, cur.path));
247
- return out;
248
- }
249
-
250
- /** Every path an apply_patch blob touches, in file order. */
251
- export function applyPatchFilePaths(patch: string): string[] {
252
- return splitApplyPatch(patch).map((s) => s.path);
253
- }
254
-
255
- /**
256
- * When a path list must still collapse to ONE (the overflow tail, and the
257
- * non-fan-out safety net), pick the one a builtin is most likely to deny on
258
- * rather than element [0]. The probes IMPORT the real builtin regexes — a
259
- * second hand-maintained copy is how a bypass gets reintroduced by a builtin
260
- * edit nobody thought to mirror. Falls back to paths[0] when nothing matches.
261
- *
262
- * NOT the primary mechanism, and it cannot be: it runs before params are bound,
263
- * so `block-secrets-write`'s `additionalPatterns` and `block-read-outside-cwd`'s
264
- * `allowPaths` have no influence on it, and neither does any custom policy.
265
- * That is exactly why PreToolUse fans out instead of relying on this.
266
- */
267
- export function pickRiskiestPath(paths: string[]): string | undefined {
268
- if (paths.length === 0) return undefined;
269
- for (const re of [
270
- ENV_FILE_PATH_RE,
271
- SECRET_FILE_RE,
272
- SECRET_FILE_ID_RSA_RE,
273
- SECRET_FILE_CREDENTIALS_RE,
274
- ]) {
275
- const hit = paths.find((p) => re.test(p));
276
- if (hit) return hit;
277
- }
278
- return paths[0];
279
- }
280
-
281
- /** Everything past the cap / budget, joined into ONE degraded element. It is
282
- * still EVALUATED — a weaker guarantee we can state honestly, never a silent
283
- * skip. */
284
- export function collapseElements(tool: string, els: FanoutElement[]): FanoutElement {
285
- const first = els[0]!;
286
- const base = { label: `${els.length} collapsed`, index: first.index, degraded: true as const };
287
- switch (tool) {
288
- case "Bash":
289
- return {
290
- ...base,
291
- input: {
292
- ...first.input,
293
- command: els.map((e) => String(e.input.command ?? "")).join(BATCH_JOIN),
294
- },
295
- };
296
- case "Read":
297
- case "Edit": {
298
- const paths = els.map((e) => String(e.input.file_path ?? "")).filter(Boolean);
299
- return {
300
- ...base,
301
- input: { ...first.input, file_path: pickRiskiestPath(paths), cline_collapsed_paths: paths },
302
- };
303
- }
304
- case "Grep":
305
- return {
306
- ...base,
307
- input: { ...first.input, pattern: els.map((e) => String(e.input.pattern ?? "")).join("|") },
308
- };
309
- default:
310
- return { ...first, ...base };
311
- }
312
- }
313
-
314
- /**
315
- * Cline → canonical, for every path that does NOT fan out: PostToolUse, audit
316
- * replay, fail-closed shaping, and a policy reading `ctx.payload`. Derives the
317
- * best single scalar and PRESERVES the arrays under `cline_*` so a batch-aware
318
- * policy can still see everything.
319
- *
320
- * This is a SAFETY NET, not the enforcement mechanism: a collapse cannot make
321
- * an anchored regex match every element, which is precisely why PreToolUse fans
322
- * out instead. `pickRiskiestPath` is what makes the net worth having — element
323
- * [0] would silently miss a `.env` at `files[1]`.
324
- */
325
- export function canonicalizeClineToolInput(
326
- toolName: string,
327
- input: Record<string, unknown>,
328
- ): Record<string, unknown> {
329
- switch (toolName) {
330
- case "Bash": {
331
- const cs = input.commands;
332
- if (!Array.isArray(cs)) return input;
333
- const commands = cs.filter((c): c is string => typeof c === "string");
334
- if (commands.length === 0) return input;
335
- return { ...omit(input, "commands"), command: commands.join(BATCH_JOIN), cline_commands: commands };
336
- }
337
- case "Read": {
338
- const files = input.files;
339
- if (!Array.isArray(files)) return input;
340
- const paths = files
341
- .map((f) =>
342
- f && typeof f === "object" && !Array.isArray(f)
343
- ? (f as Record<string, unknown>).path
344
- : undefined,
345
- )
346
- .filter((p): p is string => typeof p === "string");
347
- if (paths.length === 0) return input;
348
- return {
349
- ...omit(input, "files"),
350
- file_path: pickRiskiestPath(paths),
351
- cline_files: files,
352
- cline_file_paths: paths,
353
- };
354
- }
355
- case "Grep": {
356
- const qs = input.queries;
357
- if (!Array.isArray(qs)) return input;
358
- const queries = qs.filter((q): q is string => typeof q === "string");
359
- if (queries.length === 0) return input;
360
- return { ...omit(input, "queries"), pattern: queries.join("|"), cline_queries: queries };
361
- }
362
- case "Edit": {
363
- const blob = input.input;
364
- if (typeof blob !== "string") return input;
365
- const sections = splitApplyPatch(blob);
366
- if (sections.length === 0) return input;
367
- const paths = sections.map((s) => s.path);
368
- return {
369
- ...input,
370
- patch: blob,
371
- file_path: pickRiskiestPath(paths),
372
- old_string: sections.map((s) => s.oldText).join("\n"),
373
- new_string: sections.map((s) => s.newText).join("\n"),
374
- cline_patch_files: paths,
375
- };
376
- }
377
- default:
378
- return input;
379
- }
380
- }