failproofai 0.0.11-beta.7 → 0.0.11-beta.9

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 (233) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/standalone/.next/build-manifest.json +6 -6
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +6 -0
  7. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  8. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  19. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  20. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  21. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  27. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  30. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  31. package/.next/standalone/.next/server/app/api/audit/invite/route/app-paths-manifest.json +3 -0
  32. package/.next/standalone/.next/server/app/api/audit/invite/route/build-manifest.json +9 -0
  33. package/.next/standalone/.next/server/app/api/audit/invite/route/server-reference-manifest.json +4 -0
  34. package/.next/standalone/.next/server/app/api/audit/invite/route.js +7 -0
  35. package/.next/standalone/.next/server/app/api/audit/invite/route.js.map +5 -0
  36. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -0
  37. package/.next/standalone/.next/server/app/api/audit/invite/route_client-reference-manifest.js +3 -0
  38. package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
  41. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +2 -2
  43. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/logout/route.js +2 -2
  45. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +2 -2
  47. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/auth/status/route.js +2 -2
  49. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  52. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  54. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/index.html +1 -1
  57. package/.next/standalone/.next/server/app/index.rsc +15 -15
  58. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  59. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  60. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  61. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  62. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  63. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  64. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  65. package/.next/standalone/.next/server/app/page.js +2 -2
  66. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  67. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  69. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  70. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  71. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  74. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  75. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
  76. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  79. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  80. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  81. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +3 -3
  82. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  84. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  85. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  86. package/.next/standalone/.next/server/app/projects/page.js +3 -3
  87. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  88. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  89. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  90. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_mqemn._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1r1h8v9._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1uatkiv._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1y6gxxb._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_audit_invite_route_actions_0-2n5sy.js +3 -0
  96. package/.next/standalone/.next/server/chunks/node_modules_0-tu4ot._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/node_modules_0ttxbz7._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/node_modules_1bnh1y0._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/node_modules_1epycqa._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/node_modules_1wpdcgo._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +8 -8
  102. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0808sha._.js +4 -0
  106. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1cd25c7._.js → [root-of-the-server]__0e4-6d8._.js} +3 -3
  107. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__08gyb62._.js → [root-of-the-server]__0e446gb._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ehe24g._.js +4 -0
  109. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-scthx._.js → [root-of-the-server]__0f62vu9._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g253ve._.js +4 -0
  111. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l13qf2._.js → [root-of-the-server]__0k65l27._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vxf0_g._.js +4 -0
  113. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__08v7sn2._.js → [root-of-the-server]__0wprfyc._.js} +2 -2
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12mcauo._.js +4 -0
  115. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0989_dx._.js → [root-of-the-server]__1e-x7j4._.js} +2 -2
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mt35_w._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pcxxwg._.js +2 -2
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1uvfwgr._.js +4 -0
  119. package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/_0il3fl1._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +1 -1
  123. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +21 -21
  124. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  126. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0y4a-0q.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  129. package/.next/standalone/.next/server/functions-config-manifest.json +0 -3
  130. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  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 +10 -10
  135. package/.next/standalone/.next/static/chunks/{0hmxjseydp6as.js → 07_d165p5h5ys.js} +1 -1
  136. package/.next/standalone/.next/static/chunks/0f7d7hnbh4djs.js +1 -0
  137. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +1 -0
  138. package/.next/standalone/.next/static/chunks/168k-8z6k7e8z.css +1 -0
  139. package/.next/standalone/.next/static/chunks/{3n28elq6kwpy3.js → 1kvadxkgnapyj.js} +1 -1
  140. package/.next/standalone/.next/static/chunks/{0fpw1jzeg66_x.js → 277oc363p56n6.js} +2 -2
  141. package/.next/standalone/.next/static/chunks/28mkxkl_d91-l.js +1 -0
  142. package/.next/standalone/.next/static/chunks/28x7jvo3kxd3u.js +41 -0
  143. package/.next/standalone/.next/static/chunks/29nrs5xs9c4hx.css +2 -0
  144. package/.next/standalone/.next/static/chunks/{41vwoxpkauj1w.js → 29tg7deqmq32l.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/{3w8d8k_dca5rp.js → 2h0dkzyy0vocp.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/{1eq7f419t6ln3.js → 2z42u62k-8-_q.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/{0avgb78edu0i-.js → 3nj6g3xu9uy78.js} +1 -1
  148. package/.next/standalone/.next/static/chunks/{0xkxbaa-oo180.js → 3ty6dhcuogout.js} +1 -1
  149. package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
  150. package/.next/standalone/.next/static/chunks/{turbopack-00qy7zfa7m--m.js → turbopack-3lrm4f20fz89b.js} +1 -1
  151. package/.next/standalone/app/api/audit/_state.ts +40 -33
  152. package/.next/standalone/app/api/audit/invite/route.ts +183 -0
  153. package/.next/standalone/app/api/audit/run/route.ts +30 -16
  154. package/.next/standalone/app/api/audit/status/route.ts +1 -0
  155. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +33 -65
  156. package/.next/standalone/app/audit/_components/audit-poster.tsx +322 -0
  157. package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
  158. package/.next/standalone/app/audit/_components/auth-dialog.tsx +21 -49
  159. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +316 -0
  160. package/.next/standalone/app/audit/_components/empty-state.tsx +8 -2
  161. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +187 -0
  162. package/.next/standalone/app/audit/_components/invite-dialog.tsx +227 -0
  163. package/.next/standalone/app/audit/_components/quirks-section.tsx +75 -0
  164. package/.next/standalone/app/audit/_components/rerun-button.tsx +43 -14
  165. package/.next/standalone/app/audit/_components/run-progress.tsx +2 -2
  166. package/.next/standalone/app/audit/_components/share-templates.ts +23 -22
  167. package/.next/standalone/app/audit/_components/sigil.tsx +9 -66
  168. package/.next/standalone/app/audit/_components/strengths-section.tsx +20 -32
  169. package/.next/standalone/app/audit/audit-styles.css +781 -1784
  170. package/.next/standalone/app/components/sessions-list.tsx +77 -80
  171. package/.next/standalone/app/globals.css +214 -32
  172. package/.next/standalone/app/layout.tsx +1 -10
  173. package/.next/standalone/app/policies/hooks-client.tsx +12 -4
  174. package/.next/standalone/app/project/[name]/page.tsx +23 -79
  175. package/.next/standalone/app/projects/page.tsx +14 -23
  176. package/.next/standalone/assets/audit/poster-styles.css +1 -1
  177. package/.next/standalone/assets/audit/styles.css +11 -11
  178. package/.next/standalone/components/navbar.tsx +2 -37
  179. package/.next/standalone/lib/auth/api-server-client.ts +25 -0
  180. package/.next/standalone/node_modules/@next/env/package.json +2 -2
  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/lib/patch-incorrect-lockfile.js +3 -3
  184. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  185. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  186. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  187. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  188. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  189. package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  190. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  191. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  192. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  193. package/.next/standalone/node_modules/next/package.json +15 -15
  194. package/.next/standalone/package.json +18 -15
  195. package/.next/standalone/server.js +1 -1
  196. package/dist/cli.mjs +2 -2
  197. package/lib/auth/api-server-client.ts +25 -0
  198. package/package.json +18 -15
  199. package/src/audit/dashboard-cache.ts +13 -6
  200. package/src/audit/social-proof.ts +34 -0
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07tgnzi._.js +0 -3
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oeun7z._.js +0 -3
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12pit4m._.js +0 -3
  204. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13ra2jq._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1b9z5-i._.js +0 -3
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ixjiy8._.js +0 -3
  207. package/.next/standalone/.next/server/chunks/_1-1804f._.js +0 -3
  208. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00jkjmt._.js +0 -4
  209. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013du6r._.js +0 -4
  210. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e85wxv._.js +0 -4
  211. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gfxvb1._.js +0 -3
  212. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1hlrq6y._.js +0 -4
  213. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ihxdo5._.js +0 -4
  214. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1vvfde2._.js +0 -4
  215. package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_0q4m0_h.js +0 -3
  216. package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_1n-0xws.js +0 -3
  217. package/.next/standalone/.next/static/chunks/09ueq8s1as8xs.css +0 -2
  218. package/.next/standalone/.next/static/chunks/0qassxjx1ef04.js +0 -1
  219. package/.next/standalone/.next/static/chunks/2452blhu4bcdr.js +0 -41
  220. package/.next/standalone/.next/static/chunks/2so39wg7mjbi7.js +0 -1
  221. package/.next/standalone/.next/static/chunks/3gti1qdk5epqn.js +0 -1
  222. package/.next/standalone/.next/static/chunks/3osmt-t2hsrng.js +0 -1
  223. package/.next/standalone/.next/static/chunks/3wycox197ouus.css +0 -1
  224. package/.next/standalone/app/audit/_components/findings-section.tsx +0 -135
  225. package/.next/standalone/app/audit/_components/identity-section.tsx +0 -126
  226. package/.next/standalone/app/audit/_components/policies-section.tsx +0 -194
  227. package/.next/standalone/app/audit/_components/return-section.tsx +0 -416
  228. package/.next/standalone/app/audit/_components/score-section.tsx +0 -179
  229. package/.next/standalone/app/audit/_components/share-dock.tsx +0 -265
  230. package/.next/standalone/app/audit/_components/show-off-cta.tsx +0 -135
  231. /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_buildManifest.js +0 -0
  232. /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_clientMiddlewareManifest.js +0 -0
  233. /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_ssgManifest.js +0 -0
@@ -0,0 +1,187 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Section 04 — HOW TO IMPROVE. Calm row list, one per prescribed
5
+ * policy:
6
+ *
7
+ * <policy-name> $ failproofai policy add <slug> [📋]
8
+ * <one-line explanation>
9
+ *
10
+ * A single "install all" button at the section header copies the
11
+ * combined install command for every prescribed policy.
12
+ */
13
+ import React, { useMemo, useState } from "react";
14
+ import type { AuditResult } from "@/src/audit/types";
15
+ import { type Grade, tierName } from "@/src/audit/scoring";
16
+ import { usePostHog } from "@/contexts/PostHogContext";
17
+
18
+ interface Props {
19
+ result: AuditResult;
20
+ projected: number;
21
+ projectedGrade: Grade;
22
+ }
23
+
24
+ const DETECTOR_TO_PRIMARY_POLICY: Record<string, string> = {
25
+ "redundant-cd-cwd": "warn-repeated-tool-calls",
26
+ "prefer-edit-over-read-cat": "block-read-outside-cwd",
27
+ "prefer-edit-over-sed-awk": "warn-repeated-tool-calls",
28
+ "prefer-write-over-heredoc": "block-env-files",
29
+ "sleep-polling-loop": "warn-background-process",
30
+ "find-from-root": "block-read-outside-cwd",
31
+ "git-commit-no-verify": "warn-git-amend",
32
+ "reread-after-edit": "warn-repeated-tool-calls",
33
+ };
34
+
35
+ const POLICY_DESC: Record<string, string> = {
36
+ "warn-repeated-tool-calls": "warns when the same tool is called 3+ times with identical parameters.",
37
+ "block-read-outside-cwd": "denies any file read outside the project root, including symlinks.",
38
+ "block-env-files": "blocks reads and writes of .env files at the tool layer.",
39
+ "block-secrets-write": "blocks writes to .pem, id_rsa, credentials.json, and other secret-key files.",
40
+ "warn-background-process": "warns before starting nohup / & / screen / tmux processes.",
41
+ "warn-git-amend": "warns before amending git commits.",
42
+ "require-ci-green-before-stop": "requires CI checks to pass on HEAD before the agent declares the task done.",
43
+ };
44
+
45
+ function shortName(name: string): string {
46
+ const slash = name.indexOf("/");
47
+ return slash >= 0 ? name.slice(slash + 1) : name;
48
+ }
49
+
50
+ interface FixRow {
51
+ name: string;
52
+ desc: string;
53
+ hits: number;
54
+ }
55
+
56
+ function buildFixes(result: AuditResult): FixRow[] {
57
+ const enabledSet = new Set(result.enabledBuiltinNames ?? []);
58
+ const buckets = new Map<string, number>();
59
+
60
+ for (const row of result.results) {
61
+ if (row.hits === 0) continue;
62
+
63
+ let target: string;
64
+ if (row.source === "audit-detector") {
65
+ const mapped = DETECTOR_TO_PRIMARY_POLICY[shortName(row.name)];
66
+ if (!mapped) continue;
67
+ target = mapped;
68
+ } else if (row.source === "builtin" && !row.enabledInConfig) {
69
+ target = shortName(row.name);
70
+ } else {
71
+ continue;
72
+ }
73
+
74
+ if (enabledSet.has(target)) continue;
75
+ buckets.set(target, (buckets.get(target) ?? 0) + row.hits);
76
+ }
77
+
78
+ return [...buckets.entries()]
79
+ .sort((a, b) => b[1] - a[1])
80
+ .map(([name, hits]) => ({
81
+ name,
82
+ desc: POLICY_DESC[name] ?? "enable this builtin policy to close the gap.",
83
+ hits,
84
+ }));
85
+ }
86
+
87
+ function bulkInstall(fixes: FixRow[]): string {
88
+ if (fixes.length === 0) return "";
89
+ if (fixes.length === 1) return `failproofai policy add ${fixes[0]!.name}`;
90
+ return `failproofai policy add ${fixes.map((f) => f.name).join(" ")}`;
91
+ }
92
+
93
+ export function HowToImproveSection({ result, projected, projectedGrade }: Props) {
94
+ const { capture } = usePostHog();
95
+ const fixes = useMemo(() => buildFixes(result), [result]);
96
+ const installAllCmd = useMemo(() => bulkInstall(fixes), [fixes]);
97
+ const [copiedAll, setCopiedAll] = useState(false);
98
+
99
+ if (fixes.length === 0) return null;
100
+
101
+ const handleInstallAll = async () => {
102
+ try {
103
+ await navigator.clipboard.writeText(installAllCmd);
104
+ setCopiedAll(true);
105
+ capture("audit_copy_clicked", {
106
+ source: "how_to_improve_section_install_all",
107
+ item_type: "bulk_install_command",
108
+ policy_count: fixes.length,
109
+ });
110
+ setTimeout(() => setCopiedAll(false), 1500);
111
+ } catch { /* ignore */ }
112
+ };
113
+
114
+ return (
115
+ <section className="audit-sec" data-screen-label="04 How to improve">
116
+ <div className="audit-sec-head">
117
+ <span className="audit-sec-eyebrow">
118
+ <span className="ix">04</span>{"// how to improve"}
119
+ </span>
120
+ <button
121
+ type="button"
122
+ className="install-all-btn"
123
+ onClick={handleInstallAll}
124
+ aria-label="Copy install-all command"
125
+ >
126
+ {copiedAll ? "copied" : "install all"}
127
+ </button>
128
+ </div>
129
+ <h2 className="audit-sec-title">install or configure</h2>
130
+ <div className="audit-sec-sub">
131
+ enable all {fixes.length === 1 ? "one" : fixes.length} → projected{" "}
132
+ <strong>{projected}</strong> · {tierName(projectedGrade).toLowerCase()}
133
+ </div>
134
+
135
+ <div className="fix-list">
136
+ {fixes.map((f, i) => (
137
+ <FixRow key={f.name} fix={f} idx={i} />
138
+ ))}
139
+ </div>
140
+ </section>
141
+ );
142
+ }
143
+
144
+ function FixRow({ fix, idx }: { fix: FixRow; idx: number }) {
145
+ const { capture } = usePostHog();
146
+ const [copied, setCopied] = useState(false);
147
+ const install = `failproofai policy add ${fix.name}`;
148
+
149
+ const handleCopy = async () => {
150
+ try {
151
+ await navigator.clipboard.writeText(install);
152
+ setCopied(true);
153
+ capture("audit_copy_clicked", {
154
+ source: "how_to_improve_section",
155
+ item_type: "single_policy_install_command",
156
+ policy_name: fix.name,
157
+ policy_rank: idx + 1,
158
+ });
159
+ setTimeout(() => setCopied(false), 1500);
160
+ } catch { /* ignore */ }
161
+ };
162
+
163
+ return (
164
+ <div className="fix-row">
165
+ <div className="fix-row-info">
166
+ <div className="fix-name">{fix.name}</div>
167
+ <div className="fix-desc">{fix.desc}</div>
168
+ </div>
169
+ <div className="fix-row-cmd">
170
+ <code className="fix-cmd-code">{install}</code>
171
+ <button
172
+ type="button"
173
+ className="copy-icon-btn"
174
+ onClick={handleCopy}
175
+ aria-label={`Copy install command for ${fix.name}`}
176
+ >
177
+ {copied ? "✓" : (
178
+ <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
179
+ <rect x="3" y="3" width="9" height="11" fill="none" stroke="currentColor" strokeWidth="1.2" />
180
+ <rect x="5.5" y="0.5" width="9" height="11" fill="none" stroke="currentColor" strokeWidth="1.2" />
181
+ </svg>
182
+ )}
183
+ </button>
184
+ </div>
185
+ </div>
186
+ );
187
+ }
@@ -0,0 +1,227 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Invite dialog — modal that takes a comma/newline-separated list of friend
5
+ * emails and POSTs them to /api/audit/invite. The api-server composes the
6
+ * actual invite email (Cc'ing the sender) using the same email infrastructure
7
+ * that backs the OTP flow.
8
+ *
9
+ * Anonymous users get bounced through the AuthDialog by the caller; this
10
+ * component assumes the session is already established.
11
+ */
12
+
13
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
14
+ import { usePostHog } from "@/contexts/PostHogContext";
15
+ import { toast } from "@/app/components/toast";
16
+
17
+ interface Props {
18
+ open: boolean;
19
+ onClose: () => void;
20
+ /** Source tag for PostHog so we can split "invited from come-back-better"
21
+ * from any future entry points. */
22
+ source: string;
23
+ /** Called when the proxy returns 401 (session expired between probe and
24
+ * submit). The parent should re-open the AuthDialog so the user can
25
+ * re-authenticate; without this, a 401 dead-ends with an inline error. */
26
+ onUnauthorized?: () => void;
27
+ }
28
+
29
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
30
+ const MAX_RECIPIENTS = 10;
31
+
32
+ function parseEmails(input: string): { valid: string[]; invalid: string[] } {
33
+ const tokens = input
34
+ .split(/[\s,;]+/)
35
+ .map((t) => t.trim().toLowerCase())
36
+ .filter(Boolean);
37
+ const valid: string[] = [];
38
+ const invalid: string[] = [];
39
+ const seen = new Set<string>();
40
+ for (const t of tokens) {
41
+ if (seen.has(t)) continue;
42
+ seen.add(t);
43
+ if (EMAIL_RE.test(t)) valid.push(t);
44
+ else invalid.push(t);
45
+ }
46
+ return { valid, invalid };
47
+ }
48
+
49
+ export function InviteDialog({ open, onClose, source, onUnauthorized }: Props): React.ReactElement | null {
50
+ const { capture } = usePostHog();
51
+ const [value, setValue] = useState("");
52
+ const [busy, setBusy] = useState(false);
53
+ const [error, setError] = useState<string | null>(null);
54
+ const inputRef = useRef<HTMLTextAreaElement | null>(null);
55
+
56
+ useEffect(() => {
57
+ if (open) {
58
+ setValue("");
59
+ setBusy(false);
60
+ setError(null);
61
+ capture("audit_invite_dialog_opened", { source });
62
+ }
63
+ }, [capture, open, source]);
64
+
65
+ useEffect(() => {
66
+ if (!open) return;
67
+ const t = setTimeout(() => inputRef.current?.focus(), 50);
68
+ return () => clearTimeout(t);
69
+ }, [open]);
70
+
71
+ useEffect(() => {
72
+ if (!open) return;
73
+ const onKey = (e: KeyboardEvent): void => {
74
+ if (e.key === "Escape" && !busy) onClose();
75
+ };
76
+ window.addEventListener("keydown", onKey);
77
+ return () => window.removeEventListener("keydown", onKey);
78
+ }, [open, busy, onClose]);
79
+
80
+ const { valid, invalid } = useMemo(() => parseEmails(value), [value]);
81
+
82
+ const submit = useCallback(
83
+ async (e: React.FormEvent<HTMLFormElement>) => {
84
+ e.preventDefault();
85
+ if (busy) return;
86
+ if (valid.length === 0) {
87
+ setError("add at least one valid email address.");
88
+ return;
89
+ }
90
+ if (valid.length > MAX_RECIPIENTS) {
91
+ setError(`up to ${MAX_RECIPIENTS} emails at a time. send the rest in a follow-up.`);
92
+ return;
93
+ }
94
+ setBusy(true);
95
+ setError(null);
96
+ capture("audit_invite_submitted", {
97
+ source,
98
+ recipient_count: valid.length,
99
+ had_invalid: invalid.length > 0,
100
+ });
101
+ try {
102
+ const res = await fetch("/api/audit/invite", {
103
+ method: "POST",
104
+ headers: { "content-type": "application/json" },
105
+ body: JSON.stringify({ to: valid }),
106
+ });
107
+ const body = (await res.json().catch(() => ({}))) as {
108
+ sent?: string[];
109
+ failed?: string[];
110
+ code?: string;
111
+ message?: string;
112
+ };
113
+ if (res.status === 401) {
114
+ // Session expired between probe and submit — route back through auth.
115
+ // Without this, repeated submits would dead-end with the same 401.
116
+ if (onUnauthorized) {
117
+ onClose();
118
+ onUnauthorized();
119
+ } else {
120
+ setError("session expired. please sign in again.");
121
+ }
122
+ return;
123
+ }
124
+ if (!res.ok) {
125
+ const msg = body.message ?? "couldn't send invites.";
126
+ setError(msg);
127
+ return;
128
+ }
129
+ const sent = body.sent?.length ?? 0;
130
+ const failed = body.failed?.length ?? 0;
131
+ toast(
132
+ failed > 0
133
+ ? `📨 sent ${sent}, ${failed} bounced — copy the bounce and try again.`
134
+ : `📨 sent ${sent} ${sent === 1 ? "invite" : "invites"}. thanks for spreading the word.`,
135
+ );
136
+ onClose();
137
+ } catch (err) {
138
+ const message = err instanceof Error ? err.message : String(err);
139
+ setError(`network error: ${message}`);
140
+ } finally {
141
+ setBusy(false);
142
+ }
143
+ },
144
+ [busy, valid, invalid, capture, source, onClose, onUnauthorized],
145
+ );
146
+
147
+ if (!open) return null;
148
+
149
+ return (
150
+ <div
151
+ className="auth-dialog-backdrop"
152
+ role="dialog"
153
+ aria-modal="true"
154
+ aria-labelledby="invite-dialog-title"
155
+ onClick={(e) => {
156
+ if (!busy && e.target === e.currentTarget) onClose();
157
+ }}
158
+ >
159
+ <div className="auth-dialog">
160
+ <button
161
+ type="button"
162
+ className="auth-close"
163
+ onClick={onClose}
164
+ disabled={busy}
165
+ aria-label="close"
166
+ >
167
+ ×
168
+ </button>
169
+
170
+ <h2 id="invite-dialog-title" className="auth-headline">
171
+ invite friends to audit theirs
172
+ </h2>
173
+ <p className="auth-sub">
174
+ paste emails separated by commas, spaces, or newlines. you&apos;ll be Cc&apos;d on every invite so they know it&apos;s from you.
175
+ </p>
176
+
177
+ <form onSubmit={submit} className="auth-form">
178
+ <textarea
179
+ ref={inputRef}
180
+ name="emails"
181
+ placeholder="alice@x.com, bob@y.com&#10;carol@z.com"
182
+ disabled={busy}
183
+ className="auth-input invite-textarea"
184
+ value={value}
185
+ onChange={(e) => setValue(e.target.value)}
186
+ rows={4}
187
+ required
188
+ />
189
+ <div className="invite-summary">
190
+ {valid.length > 0 && (
191
+ <span className="invite-valid">{valid.length} valid</span>
192
+ )}
193
+ {invalid.length > 0 && (
194
+ <span className="invite-invalid">
195
+ {invalid.length} invalid: {invalid.slice(0, 3).join(", ")}
196
+ {invalid.length > 3 ? `, +${invalid.length - 3} more` : ""}
197
+ </span>
198
+ )}
199
+ {valid.length > MAX_RECIPIENTS && (
200
+ <span className="invite-invalid">
201
+ only {MAX_RECIPIENTS} per send — first {MAX_RECIPIENTS} will go.
202
+ </span>
203
+ )}
204
+ </div>
205
+ {error && <div className="auth-error">{error}</div>}
206
+ <div className="auth-actions">
207
+ <button
208
+ type="submit"
209
+ className="auth-btn primary"
210
+ disabled={busy || valid.length === 0}
211
+ >
212
+ {busy ? "sending…" : `send ${valid.length || ""} invite${valid.length === 1 ? "" : "s"}`.trim()}
213
+ </button>
214
+ <button
215
+ type="button"
216
+ className="auth-btn"
217
+ onClick={onClose}
218
+ disabled={busy}
219
+ >
220
+ cancel
221
+ </button>
222
+ </div>
223
+ </form>
224
+ </div>
225
+ </div>
226
+ );
227
+ }
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Section 03 — QUIRKS. "what to improve."
5
+ *
6
+ * 4-column table: when · what slipped (+ policy that would've caught it) ·
7
+ * severity pill · recurrence. Each row corresponds to a triggered
8
+ * detector. No per-finding card chrome, no 4-quad body, no corner
9
+ * crosshairs — evidence and fix live in section 04 (How to improve).
10
+ */
11
+ import React from "react";
12
+ import type { FindingCard } from "@/src/audit/findings";
13
+
14
+ interface Props {
15
+ findings: FindingCard[];
16
+ }
17
+
18
+ type Severity = "low" | "medium" | "high";
19
+
20
+ /** Heuristic severity from occurrence count — until the audit pipeline
21
+ * carries a real severity field. Tuned so a single occurrence reads as
22
+ * low and >5 reads as high. */
23
+ function severityFromCount(count: number): Severity {
24
+ if (count >= 6) return "high";
25
+ if (count >= 2) return "medium";
26
+ return "low";
27
+ }
28
+
29
+ function recurrenceLabel(count: number): string {
30
+ if (count <= 1) return "new";
31
+ if (count < 10) return `${count}× seen`;
32
+ return "recurring";
33
+ }
34
+
35
+ export function QuirksSection({ findings }: Props) {
36
+ if (findings.length === 0) return null;
37
+
38
+ return (
39
+ <section className="audit-sec" data-screen-label="03 Quirks">
40
+ <div className="audit-sec-head">
41
+ <span className="audit-sec-eyebrow">
42
+ <span className="ix">03</span>{"// quirks"}
43
+ </span>
44
+ <span className="audit-sec-meta">{findings.length} slipped through</span>
45
+ </div>
46
+ <h2 className="audit-sec-title">what to improve</h2>
47
+
48
+ <div className="quirks-table">
49
+ <div className="quirks-thead">
50
+ <span>when</span>
51
+ <span>what slipped</span>
52
+ <span>severity</span>
53
+ <span>seen</span>
54
+ </div>
55
+ {findings.map((f) => {
56
+ const sev = severityFromCount(f.count);
57
+ return (
58
+ <div key={f.sourceSlug} className="quirks-row">
59
+ <span className="q-when">{f.lastSeen}</span>
60
+ <span className="q-what">
61
+ <span className="q-title">{f.title}</span>
62
+ <span className="q-policy">
63
+ would&apos;ve been caught by:{" "}
64
+ <code>{f.policy}</code>
65
+ </span>
66
+ </span>
67
+ <span className={`q-pill q-pill-${sev}`}>{sev}</span>
68
+ <span className="q-recur">{recurrenceLabel(f.count)}</span>
69
+ </div>
70
+ );
71
+ })}
72
+ </div>
73
+ </section>
74
+ );
75
+ }
@@ -30,7 +30,13 @@ export interface ScanParams {
30
30
  }
31
31
 
32
32
  const POLL_INTERVAL_MS = 1000;
33
- const MAX_POLL_MS = 5 * 60_000; // 5 min hard cap
33
+
34
+ /** Give up polling only after this many *consecutive* status-fetch failures.
35
+ * There is no duration cap on a run (a cold all-history scan is unbounded), so
36
+ * the only thing that should stop a live poll is losing the server — not the
37
+ * run taking a while. At POLL_INTERVAL_MS, this is ~10s of an unreachable
38
+ * server before we surface a network error. */
39
+ const MAX_CONSECUTIVE_POLL_FAILURES = 10;
34
40
 
35
41
  /** Exported for unit testing the option-threading. */
36
42
  export function paramsToBody(p: ScanParams) {
@@ -51,7 +57,10 @@ export class RerunError extends Error {
51
57
  }
52
58
 
53
59
  export async function triggerRun(scanParams: ScanParams): Promise<void> {
54
- // Kick off the run. 409 (already running) is OKwe'll just poll.
60
+ // Start the run. The route is fire-and-forgetit kicks off runAudit() in
61
+ // the background and returns 202 in milliseconds — so the default fast fetch
62
+ // timeout is correct here: it bounds the *kickoff request*, not the run.
63
+ // 409 (already running) is fine — we just poll the in-flight run.
55
64
  try {
56
65
  const res = await fetchWithTimeout("/api/audit/run", {
57
66
  method: "POST",
@@ -69,22 +78,42 @@ export async function triggerRun(scanParams: ScanParams): Promise<void> {
69
78
  throw new RerunError(isAbortError(err) ? "timeout" : "network", "audit run request failed");
70
79
  }
71
80
 
72
- // Poll status until running flips false. The status route returns
73
- // `cachedAt`, which the polling caller can compare against the prior
74
- // value to detect "fresh result available" but for `triggerRun`'s
75
- // purposes the `running: false` flip is the success signal.
76
- const startedAt = Date.now();
77
- while (Date.now() - startedAt < MAX_POLL_MS) {
81
+ // Poll /api/audit/status until the run finishes. There is deliberately NO
82
+ // duration cap: a cold, all-history scan can run arbitrarily long and must
83
+ // not be guillotined client-side. The only failure mode that stops polling is
84
+ // losing the server, tracked as consecutive status-fetch failures.
85
+ let consecutiveFailures = 0;
86
+ for (;;) {
78
87
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
88
+
89
+ let status: { running: boolean; error?: string | null } | null = null;
79
90
  try {
80
91
  const sres = await fetchWithTimeout("/api/audit/status", { cache: "no-store" });
81
- if (!sres.ok) continue;
82
- const s = await sres.json() as { running: boolean };
83
- if (!s.running) return;
92
+ if (sres.ok) {
93
+ status = (await sres.json()) as { running: boolean; error?: string | null };
94
+ }
84
95
  } catch {
85
- // Transient (including per-request timeout) — keep polling until the
86
- // outer MAX_POLL_MS budget runs out.
96
+ // Transient fetch/JSON failure (including a per-request timeout) — handled
97
+ // by the connectivity backstop below.
98
+ }
99
+
100
+ if (status === null) {
101
+ // Status unreachable or non-OK. Only give up once the server has been
102
+ // unreachable for MAX_CONSECUTIVE_POLL_FAILURES polls in a row; a single
103
+ // blip must not kill a long, healthy run.
104
+ if (++consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
105
+ throw new RerunError("network", "audit status poll lost the server");
106
+ }
107
+ continue;
108
+ }
109
+
110
+ consecutiveFailures = 0;
111
+ if (!status.running) {
112
+ // Run finished. A non-null error means the background runAudit() threw —
113
+ // surface it (the dashboard cache was NOT updated). Otherwise it's a clean
114
+ // success: writeDashboardCache ran before finishRun(null).
115
+ if (status.error) throw new RerunError("post_failed", status.error);
116
+ return;
87
117
  }
88
118
  }
89
- throw new RerunError("timeout", "audit poll loop timed out");
90
119
  }
@@ -66,7 +66,7 @@ export function RunProgress() {
66
66
  <div className="panel running-panel">
67
67
  <div className="running-header">
68
68
  <span className="running-prompt">$</span>
69
- <span className="running-cmd">failproofai audit --since 30d</span>
69
+ <span className="running-cmd">failproofai audit</span>
70
70
  <span className="running-cursor" aria-hidden="true">▮</span>
71
71
  </div>
72
72
 
@@ -112,7 +112,7 @@ export function RunProgress() {
112
112
  </div>
113
113
 
114
114
  <p className="running-foot">
115
- this usually takes 10–30 seconds depending on how much session history you have.
115
+ this can take a while depending on how much session history you have.
116
116
  </p>
117
117
  </div>
118
118
  </section>
@@ -22,32 +22,33 @@ export interface ShareCtx {
22
22
 
23
23
  const pol = (n: number) => (n === 1 ? "policy" : "policies");
24
24
 
25
- /** Quirky, emoji-forward — for X. */
25
+ /** Quirky, emoji-forward — for X. No grade-tier framing (sounds bad at
26
+ * the low end). Lean on the persona name + score number instead. */
26
27
  export const X_TEMPLATES: ((c: ShareCtx) => string)[] = [
27
- ({ score, arch, grade, missing }) =>
28
- `my AI coding agent just got profiled and it's "${arch}" 🤠\n\n${score}/100 · ${grade} tier${missing > 0 ? ` · ${missing} ${pol(missing)} between me and glory` : ` · every guardrail live`}\n\nwhat's yours? → ${SITE_URL}`,
29
- ({ score, arch, grade }) =>
30
- `turns out my coding agent has a whole personality and it's ${arch} 💀\n\nscored ${score}/100 (${grade} tier). the audit does not miss.\n\nrun yours in 30s → ${SITE_URL}`,
31
- ({ score, arch, grade }) =>
32
- `${score}/100. ${grade} tier. archetype: ${arch} 👀\n\nfailproofai reverse-engineered my agent's entire vibe from its own session logs.\n\n${SITE_URL}`,
33
- ({ score, arch, grade, missing }) =>
34
- `plot twist: my AI agent is ${arch} 🎭\n\n${score}/100 · ${grade} tier${missing > 0 ? ` · ${missing} ${pol(missing)} away from behaving` : ` · spotless, somehow`}\n\nbefailproof.ai`,
35
- ({ score, arch, grade }) =>
36
- `i let failproofai audit my coding agent and it called me ${arch} ${grade === "S" || grade === "A" ? "😎" : "😬"}\n\n${score}/100, ${grade} tier. brutally accurate. no notes.\n\n${SITE_URL}`,
28
+ ({ score, arch, missing }) =>
29
+ `my AI coding agent just got profiled and it's "${arch}" 🤠\n\nscored ${score}/100${missing > 0 ? ` · ${missing} ${pol(missing)} between me and a clean run` : ` · every guardrail live`}\n\nwhat's yours? → ${SITE_URL}`,
30
+ ({ score, arch }) =>
31
+ `turns out my coding agent has a whole personality and it's ${arch} 💀\n\n${score}/100. the audit does not miss.\n\nrun yours in 30s → ${SITE_URL}`,
32
+ ({ score, arch }) =>
33
+ `archetype: ${arch} 👀\n\n${score}/100 on the failproofai audit. it reverse-engineered my agent's entire vibe from its own session logs.\n\n${SITE_URL}`,
34
+ ({ score, arch, missing }) =>
35
+ `plot twist: my AI agent is ${arch} 🎭\n\n${score}/100${missing > 0 ? ` · ${missing} ${pol(missing)} away from behaving` : ` · spotless, somehow`}\n\nbefailproof.ai`,
36
+ ({ score, arch }) =>
37
+ `i let failproofai audit my coding agent and it called me ${arch} ${score >= 80 ? "😎" : "😬"}\n\n${score}/100. brutally accurate. no notes.\n\n${SITE_URL}`,
37
38
  ];
38
39
 
39
- /** Measured, professional — for LinkedIn. */
40
+ /** Measured, professional — for LinkedIn. No grade-tier framing. */
40
41
  export const LI_TEMPLATES: ((c: ShareCtx) => string)[] = [
41
- ({ score, arch, grade, missing }) =>
42
- `I ran a failproofai audit on our AI coding agents.\n\nResult: ${score}/100 — ${grade} tier, behavioural archetype "${arch}". ${missing > 0 ? `${missing} prescribed ${pol(missing)} would close the remaining gaps.` : `Every prescribed policy is already live.`}\n\nUnderstanding how agents actually behave across real sessions is the first step to securing them. Free and open-source: ${SITE_URL}`,
43
- ({ score, arch, grade, missing }) =>
44
- `Security posture check on our coding-agent stack, via failproofai.\n\nScore: ${score}/100 (${grade} tier). Behavioural profile: "${arch}". ${missing > 0 ? `${missing} ${pol(missing)} flagged as real, addressable attack surface.` : `No open policy gaps.`}\n\nThirty seconds to run your own assessment: ${SITE_URL}`,
45
- ({ score, arch, grade }) =>
46
- `Most teams can't answer a simple question: how do our AI agents actually behave when no one is watching?\n\nfailproofai audited ours and profiled it as "${arch}" — ${score}/100, ${grade} tier. Concrete, evidence-backed, and mapped to the exact policies that close each gap.\n\n${SITE_URL}`,
47
- ({ score, arch, grade, missing }) =>
48
- `Agent security isn't a vibe — it's measurable.\n\nWe scored ${score}/100 (${grade} tier) on the failproofai audit, archetype "${arch}". ${missing > 0 ? `${missing} prescribed ${pol(missing)} remain to harden the stack.` : `Full coverage on every prescribed policy.`}\n\nWorth 30 seconds for any team shipping with AI agents: ${SITE_URL}`,
49
- ({ score, arch, grade }) =>
50
- `Ran our coding agents through a failproofai behavioural audit.\n\n${score}/100, ${grade} tier — archetype "${arch}". The report maps every risky pattern to the policy that prevents it, so remediation becomes a checklist rather than a guess.\n\nOpen-source, free to run: ${SITE_URL}`,
42
+ ({ score, arch, missing }) =>
43
+ `I ran a failproofai audit on our AI coding agents.\n\nResult: ${score}/100, behavioural archetype "${arch}". ${missing > 0 ? `${missing} prescribed ${pol(missing)} would close the remaining gaps.` : `Every prescribed policy is already live.`}\n\nUnderstanding how agents actually behave across real sessions is the first step to securing them. Free and open-source: ${SITE_URL}`,
44
+ ({ score, arch, missing }) =>
45
+ `Security posture check on our coding-agent stack, via failproofai.\n\nScore: ${score}/100. Behavioural profile: "${arch}". ${missing > 0 ? `${missing} ${pol(missing)} flagged as real, addressable attack surface.` : `No open policy gaps.`}\n\nThirty seconds to run your own assessment: ${SITE_URL}`,
46
+ ({ score, arch }) =>
47
+ `Most teams can't answer a simple question: how do our AI agents actually behave when no one is watching?\n\nfailproofai audited ours and profiled it as "${arch}" — ${score}/100. Concrete, evidence-backed, and mapped to the exact policies that close each gap.\n\n${SITE_URL}`,
48
+ ({ score, arch, missing }) =>
49
+ `Agent security isn't a vibe — it's measurable.\n\nWe scored ${score}/100 on the failproofai audit, archetype "${arch}". ${missing > 0 ? `${missing} prescribed ${pol(missing)} remain to harden the stack.` : `Full coverage on every prescribed policy.`}\n\nWorth 30 seconds for any team shipping with AI agents: ${SITE_URL}`,
50
+ ({ score, arch }) =>
51
+ `Ran our coding agents through a failproofai behavioural audit.\n\n${score}/100, archetype "${arch}". The report maps every risky pattern to the policy that prevents it, so remediation becomes a checklist rather than a guess.\n\nOpen-source, free to run: ${SITE_URL}`,
51
52
  ];
52
53
 
53
54
  /** djb2 hash — stable per seed so the template choice is deterministic. */