failproofai 0.0.10 → 0.0.11-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 (279) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +7 -7
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +4 -4
  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 +4 -4
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  13. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +4 -4
  18. package/.next/standalone/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
  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 +4 -4
  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/download/[project]/[session]/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/index.html +1 -1
  34. package/.next/standalone/.next/server/app/index.rsc +16 -16
  35. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  36. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
  37. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  38. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  39. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +3 -3
  40. package/.next/standalone/.next/server/app/page/build-manifest.json +4 -4
  41. package/.next/standalone/.next/server/app/page/next-font-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  43. package/.next/standalone/.next/server/app/page.js +4 -4
  44. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +4 -4
  47. package/.next/standalone/.next/server/app/policies/page/next-font-manifest.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  49. package/.next/standalone/.next/server/app/policies/page.js +4 -4
  50. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +4 -4
  53. package/.next/standalone/.next/server/app/project/[name]/page/next-font-manifest.json +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -4
  56. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +4 -4
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/next-font-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -4
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +4 -4
  66. package/.next/standalone/.next/server/app/projects/page/next-font-manifest.json +1 -1
  67. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  68. package/.next/standalone/.next/server/app/projects/page.js +4 -4
  69. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_ob4n._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__044xt9.._.js → [root-of-the-server]__0fwb7ao._.js} +2 -2
  73. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g48iv.._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0j8-xkl._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0bdfoky.js +1 -1
  76. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_05pz9._._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wn51s._.js +4 -0
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01as125._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__098zro9._.js +19 -0
  81. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__02r.cjq._.js → [root-of-the-server]__09v.ljl._.js} +2 -2
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0agrcb8._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0b7hkr~._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ehh6vp._.js +4 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g8l0tu._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0j4l6hl._.js +3 -0
  87. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ye1w50._.js → [root-of-the-server]__0k5n2kz._.js} +3 -3
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lp08ll._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0yaqw._.js +4 -0
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0o21f.o._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t8juvy._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__10xgshr._.js → [root-of-the-server]__0tcyn68._.js} +2 -2
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ts150~._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0podumr._.js → [root-of-the-server]__0uylufv._.js} +3 -3
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +5 -5
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0~03grs._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/app_0cdqd9w._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/lib_utils_ts_068jk73._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/ssr/node_modules_0ttbz1~._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_06u0kr8._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0h9llsw._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0a_7sdg.js +2 -2
  105. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0ef3uwk.js +2 -2
  106. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0j79~gv.js +2 -2
  107. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0pbja1x.js +2 -2
  108. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0r6o0i2.js +2 -2
  109. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_11y81~_.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_12or2kf.js +2 -2
  111. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js +1 -1
  112. package/.next/standalone/.next/server/functions-config-manifest.json +2 -2
  113. package/.next/standalone/.next/server/middleware-build-manifest.js +7 -7
  114. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  115. package/.next/standalone/.next/server/next-font-manifest.json +6 -6
  116. package/.next/standalone/.next/server/pages/404.html +1 -1
  117. package/.next/standalone/.next/server/pages/500.html +1 -1
  118. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  120. package/.next/standalone/.next/static/chunks/07kpqoo7kuckx.js +6 -0
  121. package/.next/standalone/.next/static/chunks/0a40sy4tk8ioe.js +1 -0
  122. package/.next/standalone/.next/static/chunks/{12l2t63hkyo2q.js → 0azb~vy9ds_uy.js} +1 -1
  123. package/.next/standalone/.next/static/chunks/{0j171xiqge4rv.js → 0bke.~atnsbeb.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/{0lt8ko3lw.5yt.js → 0bv1oyxspkpkb.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/{179yytvmam0ug.js → 0dvhi-prcsh3~.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/0f5p9plm.aqlp.css +2 -0
  127. package/.next/standalone/.next/static/chunks/0ffvlbgzgnlw7.js +2 -0
  128. package/.next/standalone/.next/static/chunks/{150i0n26fnvso.js → 0n1n67imq.udf.js} +1 -1
  129. package/.next/standalone/.next/static/chunks/0spktq7xqab9h.js +1 -0
  130. package/.next/standalone/.next/static/chunks/{14lii11wmo450.js → 118q3uljozd5z.js} +1 -1
  131. package/.next/standalone/.next/static/chunks/{0pkl..xgo-qox.js → 11w14gnqzprir.js} +1 -1
  132. package/.next/standalone/.next/static/chunks/{0rnqmir4cd5p9.js → 17mubwtqwijpu.js} +1 -1
  133. package/.next/standalone/.next/static/chunks/{turbopack-05z7a19q43zfq.js → turbopack-0nh.aopesgj~5.js} +1 -1
  134. package/.next/standalone/.next/static/media/4fa387ec64143e14-s.0.qu-9752pffj.woff2 +0 -0
  135. package/.next/standalone/.next/static/media/5ce348bf30bf5439-s.0ee55_hj9qcer.woff2 +0 -0
  136. package/.next/standalone/.next/static/media/6306c77e7c8268e4-s.0mao5jbfbduzp.woff2 +0 -0
  137. package/.next/standalone/.next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2 +0 -0
  138. package/.next/standalone/.next/static/media/7d817b4c03b0c5f1-s.0uzt.a6d44yda.woff2 +0 -0
  139. package/.next/standalone/.next/static/media/bbc41e54d2fcbd21-s.0mvwgmnhv29no.woff2 +0 -0
  140. package/.next/standalone/.next/static/{dAuQps6jUwCz9X1Q5FFOO → tGVQM5SE3NvbVu0gbAJm7}/_clientMiddlewareManifest.js +2 -2
  141. package/.next/standalone/app/policies/hooks-client.tsx +111 -14
  142. package/.next/standalone/components/navbar.tsx +1 -1
  143. package/.next/standalone/components/reach-developers.tsx +2 -2
  144. package/.next/standalone/lib/claude-sessions.ts +181 -0
  145. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  146. package/.next/standalone/node_modules/next/dist/build/static-paths/app.js +2 -1
  147. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  148. package/.next/standalone/node_modules/next/dist/build/utils.js +2 -1
  149. package/.next/standalone/node_modules/next/dist/client/components/router-reducer/fetch-server-response.js +2 -2
  150. package/.next/standalone/node_modules/next/dist/client/components/router-reducer/set-cache-busting-search-param.js +8 -2
  151. package/.next/standalone/node_modules/next/dist/client/route-params.js +23 -6
  152. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +13 -13
  153. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +11 -11
  154. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +2 -2
  155. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +10 -10
  156. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  157. package/.next/standalone/node_modules/next/dist/server/app-render/action-handler.js +3 -6
  158. package/.next/standalone/node_modules/next/dist/server/app-render/app-render.js +62 -9
  159. package/.next/standalone/node_modules/next/dist/server/app-render/collect-segment-data.js +16 -0
  160. package/.next/standalone/node_modules/next/dist/server/app-render/create-component-tree.js +49 -19
  161. package/.next/standalone/node_modules/next/dist/server/app-render/get-script-nonce-from-header.js +8 -20
  162. package/.next/standalone/node_modules/next/dist/server/app-render/metadata-insertion/create-server-inserted-metadata.js +8 -7
  163. package/.next/standalone/node_modules/next/dist/server/app-render/use-flight-response.js +2 -2
  164. package/.next/standalone/node_modules/next/dist/server/async-storage/work-store.js +2 -1
  165. package/.next/standalone/node_modules/next/dist/server/base-server.js +13 -5
  166. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  167. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  168. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  169. package/.next/standalone/node_modules/next/dist/server/dev/static-paths-worker.js +2 -1
  170. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +22 -2
  171. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  172. package/.next/standalone/node_modules/next/dist/server/lib/is-rsc-request.js +18 -0
  173. package/.next/standalone/node_modules/next/dist/server/lib/mock-request.js +30 -5
  174. package/.next/standalone/node_modules/next/dist/server/lib/patch-set-header.js +7 -0
  175. package/.next/standalone/node_modules/next/dist/server/lib/router-server.js +6 -3
  176. package/.next/standalone/node_modules/next/dist/server/lib/router-utils/resolve-routes.js +18 -4
  177. package/.next/standalone/node_modules/next/dist/server/lib/server-ipc/utils.js +3 -1
  178. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  179. package/.next/standalone/node_modules/next/dist/server/next-server.js +1 -1
  180. package/.next/standalone/node_modules/next/dist/server/request/fallback-params.js +27 -1
  181. package/.next/standalone/node_modules/next/dist/server/route-modules/app-route/module.js +1 -0
  182. package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +11 -1
  183. package/.next/standalone/node_modules/next/dist/server/server-utils.js +19 -2
  184. package/.next/standalone/node_modules/next/dist/server/stream-utils/node-web-streams-helper.js +5 -5
  185. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +1 -1
  186. package/.next/standalone/node_modules/next/dist/server/web/adapter.js +4 -1
  187. package/.next/standalone/node_modules/next/dist/server/web/edge-route-module-wrapper.js +2 -1
  188. package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  189. package/.next/standalone/node_modules/next/dist/{server → shared/lib}/htmlescape.js +15 -0
  190. package/.next/standalone/node_modules/next/dist/shared/lib/router/routes/app.js +13 -1
  191. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/cache-busting-search-param.js +56 -10
  192. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  193. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  194. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  195. package/.next/standalone/node_modules/next/package.json +15 -15
  196. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  197. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  198. package/.next/standalone/node_modules/react/package.json +1 -1
  199. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  200. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  201. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  202. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  203. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  204. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  205. package/.next/standalone/node_modules/react-dom/package.json +2 -2
  206. package/.next/standalone/package.json +5 -5
  207. package/.next/standalone/proxy.ts +1 -1
  208. package/.next/standalone/server.js +1 -1
  209. package/README.md +4 -4
  210. package/bin/failproofai.mjs +230 -73
  211. package/dist/cli.mjs +3028 -1453
  212. package/lib/claude-sessions.ts +181 -0
  213. package/package.json +5 -5
  214. package/scripts/launch.ts +1 -1
  215. package/scripts/postinstall.mjs +89 -1
  216. package/src/audit/cache.ts +113 -0
  217. package/src/audit/cli-adapters/claude.ts +97 -0
  218. package/src/audit/cli-adapters/codex.ts +56 -0
  219. package/src/audit/cli-adapters/copilot.ts +51 -0
  220. package/src/audit/cli-adapters/cursor.ts +51 -0
  221. package/src/audit/cli-adapters/gemini.ts +51 -0
  222. package/src/audit/cli-adapters/index.ts +70 -0
  223. package/src/audit/cli-adapters/opencode.ts +52 -0
  224. package/src/audit/cli-adapters/pi.ts +51 -0
  225. package/src/audit/cli-adapters/shared.ts +85 -0
  226. package/src/audit/detectors/find-from-root.ts +27 -0
  227. package/src/audit/detectors/git-commit-no-verify.ts +22 -0
  228. package/src/audit/detectors/index.ts +33 -0
  229. package/src/audit/detectors/prefer-edit-over-read-cat.ts +31 -0
  230. package/src/audit/detectors/prefer-edit-over-sed-awk.ts +27 -0
  231. package/src/audit/detectors/prefer-write-over-heredoc.ts +36 -0
  232. package/src/audit/detectors/redundant-cd-cwd.ts +28 -0
  233. package/src/audit/detectors/reread-after-edit.ts +58 -0
  234. package/src/audit/detectors/sleep-polling-loop.ts +34 -0
  235. package/src/audit/index.ts +369 -0
  236. package/src/audit/replay.ts +121 -0
  237. package/src/audit/report.ts +349 -0
  238. package/src/audit/telemetry.ts +113 -0
  239. package/src/audit/types.ts +193 -0
  240. package/src/hooks/builtin-policies.ts +79 -1
  241. package/src/hooks/custom-hooks-loader.ts +19 -3
  242. package/src/hooks/first-run-nudge.ts +146 -0
  243. package/src/hooks/handler.ts +21 -102
  244. package/src/hooks/install-prompt.ts +34 -4
  245. package/src/hooks/manager.ts +72 -5
  246. package/src/hooks/policy-evaluator.ts +19 -4
  247. package/src/hooks/policy-registry.ts +1 -1
  248. package/src/hooks/policy-types.ts +9 -0
  249. package/src/hooks/tool-name-canonicalize.ts +65 -0
  250. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0609ezh._.js +0 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__07_-mkc._.js +0 -3
  252. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09z7o2x._.js +0 -19
  253. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_sh2n0._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e9o9ri._.js +0 -4
  255. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0l6swv1._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0logebz._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mi5ejy._.js +0 -4
  258. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0odijkc._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rkxer-._.js +0 -3
  260. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rl2kwi._.js +0 -4
  261. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vg0uey._.js +0 -4
  262. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0x5limi._.js +0 -3
  263. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10._f0s._.js +0 -4
  264. package/.next/standalone/.next/static/chunks/01q52wg_amm60.js +0 -2
  265. package/.next/standalone/.next/static/chunks/0kqar56yl~41o.js +0 -6
  266. package/.next/standalone/.next/static/chunks/0ml1.ck_5t36i.js +0 -1
  267. package/.next/standalone/.next/static/chunks/0zig0fh30t6ou.js +0 -1
  268. package/.next/standalone/.next/static/chunks/17rm86uz2nd5a.css +0 -2
  269. package/.next/standalone/.next/static/media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2 +0 -0
  270. package/.next/standalone/.next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2 +0 -0
  271. package/.next/standalone/.next/static/media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2 +0 -0
  272. package/src/auth/login.ts +0 -104
  273. package/src/auth/logout.ts +0 -50
  274. package/src/auth/token-store.ts +0 -64
  275. package/src/relay/daemon.ts +0 -362
  276. package/src/relay/pid.ts +0 -76
  277. package/src/relay/queue.ts +0 -225
  278. /package/.next/standalone/.next/static/{dAuQps6jUwCz9X1Q5FFOO → tGVQM5SE3NvbVu0gbAJm7}/_buildManifest.js +0 -0
  279. /package/.next/standalone/.next/static/{dAuQps6jUwCz9X1Q5FFOO → tGVQM5SE3NvbVu0gbAJm7}/_ssgManifest.js +0 -0
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Output renderers for `failproofai audit`:
3
+ * • formatText — ANSI table to stdout (GTM-oriented "moment of truth")
4
+ * • formatMarkdown — shareable sectioned report written to a file
5
+ * • formatJson — machine-readable
6
+ *
7
+ * The text renderer is the user's first impression of the audit. It leads
8
+ * with a headline-box, splits findings into "already protected" vs "slipping
9
+ * through" so the conversion ask is obvious, and ends with a copy-pasteable
10
+ * install command + report path + star link.
11
+ */
12
+ import type { AuditCount, AuditResult, RunAuditOptions } from "./types";
13
+
14
+ const ANSI = {
15
+ reset: "\x1B[0m",
16
+ dim: "\x1B[2m",
17
+ bold: "\x1B[1m",
18
+ red: "\x1B[31m",
19
+ yellow: "\x1B[33m",
20
+ green: "\x1B[32m",
21
+ cyan: "\x1B[36m",
22
+ magenta: "\x1B[35m",
23
+ };
24
+
25
+ /** Honor https://no-color.org / NO_COLOR=1 and FORCE_COLOR=0 by stripping all
26
+ * ANSI sequences from the final output. Detected once per renderer call. */
27
+ function noColorEnabled(): boolean {
28
+ if (process.env.NO_COLOR && process.env.NO_COLOR !== "") return true;
29
+ if (process.env.FORCE_COLOR === "0") return true;
30
+ return false;
31
+ }
32
+
33
+ function stripAnsi(s: string): string {
34
+ // eslint-disable-next-line no-control-regex
35
+ return s.replace(/\x1B\[[0-9;]*m/g, "");
36
+ }
37
+
38
+ /** Human-readable "time ago" — "30m ago", "2h ago", "3d ago", "2w ago".
39
+ * Returns "just now" for <1 minute. */
40
+ function formatTimeAgo(iso: string | undefined): string {
41
+ if (!iso) return "—";
42
+ const ms = Date.now() - new Date(iso).getTime();
43
+ if (Number.isNaN(ms) || ms < 0) return "—";
44
+ const m = Math.floor(ms / 60_000);
45
+ if (m < 1) return "just now";
46
+ if (m < 60) return `${m}m ago`;
47
+ const h = Math.floor(m / 60);
48
+ if (h < 24) return `${h}h ago`;
49
+ const d = Math.floor(h / 24);
50
+ if (d < 14) return `${d}d ago`;
51
+ const w = Math.floor(d / 7);
52
+ if (w < 8) return `${w}w ago`;
53
+ return `${Math.floor(d / 30)}mo ago`;
54
+ }
55
+
56
+ /** Width of the headline box and section dividers. Adapts to narrow terminals
57
+ * down to a 60-char floor. */
58
+ function getWidth(): number {
59
+ const cols = process.stdout.columns ?? 80;
60
+ return Math.max(60, Math.min(78, cols));
61
+ }
62
+
63
+ /** Box-drawing helpers using unicode round-corner glyphs. */
64
+ function topBorder(w: number): string { return `╭${"─".repeat(w - 2)}╮`; }
65
+ function bottomBorder(w: number): string { return `╰${"─".repeat(w - 2)}╯`; }
66
+ function boxLine(text: string, w: number): string {
67
+ const inner = w - 4; // 2 chars of border + 1 char padding each side
68
+ const visible = stripAnsi(text);
69
+ const pad = Math.max(0, inner - visible.length);
70
+ return `│ ${text}${" ".repeat(pad)} │`;
71
+ }
72
+ function divider(w: number): string { return "─".repeat(w); }
73
+
74
+ /** Short, qualified policy slug — `failproofai/foo` → `foo` for display. */
75
+ function shortName(name: string): string {
76
+ const slash = name.indexOf("/");
77
+ return slash >= 0 ? name.slice(slash + 1) : name;
78
+ }
79
+
80
+ /** Sum hits across an AuditCount[] subset. */
81
+ function sumHits(rows: AuditCount[]): number {
82
+ return rows.reduce((acc, r) => acc + r.hits, 0);
83
+ }
84
+
85
+ /** Render one row in the table-of-findings form:
86
+ * 31× Tried to read files outside your project
87
+ * Stops the agent from peeking at neighboring repos…
88
+ * Last seen 2h ago · 6 projects
89
+ * › Already enforced — failproofai is blocking these in real time.
90
+ * Example: grep -n …
91
+ */
92
+ function renderRow(r: AuditCount, opts: { showExamples?: boolean }): string[] {
93
+ const out: string[] = [];
94
+ const sev = r.severity;
95
+ const titleColor =
96
+ sev === "deny" ? ANSI.red
97
+ : sev === "warn" ? ANSI.red
98
+ : sev === "info" || sev === "instruct" ? ANSI.yellow
99
+ : ANSI.cyan;
100
+ const countStr = String(r.hits).padStart(4);
101
+ out.push(` ${titleColor}${ANSI.bold}${countStr}×${ANSI.reset} ${r.displayTitle}`);
102
+ if (r.impact) {
103
+ out.push(` ${ANSI.dim}${r.impact}${ANSI.reset}`);
104
+ }
105
+ out.push(
106
+ ` ${ANSI.dim}Last seen ${formatTimeAgo(r.lastSeen)} · ${r.projects} project${r.projects === 1 ? "" : "s"}${ANSI.reset}`,
107
+ );
108
+ if (opts.showExamples && r.examples[0]) {
109
+ out.push(` ${ANSI.dim}Example: ${r.examples[0].example}${ANSI.reset}`);
110
+ }
111
+ if (r.installHint) {
112
+ const arrowColor = r.enabledInConfig ? ANSI.green : ANSI.cyan;
113
+ out.push(` ${arrowColor}›${ANSI.reset} ${r.installHint}`);
114
+ }
115
+ out.push("");
116
+ return out;
117
+ }
118
+
119
+ export function formatText(result: AuditResult, opts: RunAuditOptions = {}): string {
120
+ const w = getWidth();
121
+ const limit = opts.limit ?? 20;
122
+ const showExamples = !!opts.showExamples;
123
+
124
+ // Split rows by enforcement state.
125
+ const enabledRows = result.results.filter((r) => r.source === "builtin" && r.enabledInConfig);
126
+ const unenabledBuiltinRows = result.results.filter((r) => r.source === "builtin" && !r.enabledInConfig);
127
+ const detectorRows = result.results.filter((r) => r.source === "audit-detector");
128
+ // "Slipping through" combines unenabled-builtins + audit-detectors, ranked by hits.
129
+ const slippingRows = [...unenabledBuiltinRows, ...detectorRows].sort((a, b) => b.hits - a.hits);
130
+
131
+ const totalProtected = sumHits(enabledRows);
132
+ const totalSlipping = sumHits(slippingRows);
133
+ const totalHits = totalProtected + totalSlipping;
134
+ const sinceLabel = result.scope.since ? `the last ${result.scope.since}` : "all time";
135
+
136
+ const lines: string[] = [];
137
+
138
+ // ── Header ──────────────────────────────────────────────────────
139
+ lines.push(`${ANSI.cyan}🛡 failproofai audit${ANSI.reset} ${ANSI.dim}[beta]${ANSI.reset} · ${sinceLabel}`);
140
+ lines.push(
141
+ ` ${ANSI.dim}${result.transcripts.scanned} sessions · ${result.totals.projectsWithHits} project${result.totals.projectsWithHits === 1 ? "" : "s"} with hits · scanned in ${(result.transcripts.durationMs / 1000).toFixed(1)}s${ANSI.reset}`,
142
+ );
143
+ lines.push("");
144
+
145
+ // ── Headline box ────────────────────────────────────────────────
146
+ if (totalHits === 0) {
147
+ lines.push(topBorder(w));
148
+ lines.push(boxLine(`${ANSI.green}🎉 Clean run!${ANSI.reset} Nothing matched your policies in this window.`, w));
149
+ lines.push(bottomBorder(w));
150
+ lines.push("");
151
+ return noColorEnabled() ? stripAnsi(lines.join("\n")) : lines.join("\n");
152
+ }
153
+ lines.push(topBorder(w));
154
+ lines.push(boxLine(
155
+ `${ANSI.bold}Your agent did ${totalHits} wasteful or risky things in ${sinceLabel}.${ANSI.reset}`,
156
+ w,
157
+ ));
158
+ if (totalSlipping > 0) {
159
+ lines.push(boxLine(
160
+ `${totalSlipping} of those would've been caught if more policies were on.`,
161
+ w,
162
+ ));
163
+ }
164
+ lines.push(bottomBorder(w));
165
+ lines.push("");
166
+
167
+ // ── Section: ALREADY PROTECTED ──────────────────────────────────
168
+ if (enabledRows.length > 0) {
169
+ lines.push(
170
+ `${ANSI.green}✓ ALREADY PROTECTED${ANSI.reset} ${ANSI.dim}(${totalProtected} action${totalProtected === 1 ? "" : "s"} stopped by your current policies)${ANSI.reset}`,
171
+ );
172
+ lines.push("");
173
+ for (const row of enabledRows.slice(0, limit)) {
174
+ lines.push(...renderRow(row, { showExamples }));
175
+ }
176
+ if (enabledRows.length > limit) {
177
+ lines.push(` ${ANSI.dim}… ${enabledRows.length - limit} more (use --limit ${enabledRows.length})${ANSI.reset}`);
178
+ lines.push("");
179
+ }
180
+ }
181
+
182
+ // ── Section: SLIPPING THROUGH ───────────────────────────────────
183
+ if (slippingRows.length > 0) {
184
+ lines.push(
185
+ `${ANSI.yellow}○ SLIPPING THROUGH${ANSI.reset} ${ANSI.dim}(${totalSlipping} action${totalSlipping === 1 ? "" : "s"} caught by audit, not blocked in real time)${ANSI.reset}`,
186
+ );
187
+ lines.push("");
188
+ for (const row of slippingRows.slice(0, limit)) {
189
+ lines.push(...renderRow(row, { showExamples }));
190
+ }
191
+ if (slippingRows.length > limit) {
192
+ lines.push(` ${ANSI.dim}… ${slippingRows.length - limit} more (use --limit ${slippingRows.length})${ANSI.reset}`);
193
+ lines.push("");
194
+ }
195
+ }
196
+
197
+ // ── Footer / NEXT step ──────────────────────────────────────────
198
+ lines.push(divider(w));
199
+ if (unenabledBuiltinRows.length > 0) {
200
+ const installNames = unenabledBuiltinRows.map((r) => shortName(r.name));
201
+ lines.push(
202
+ `${ANSI.bold}NEXT${ANSI.reset} Enable the ${installNames.length} unenabled real-time polic${installNames.length === 1 ? "y" : "ies"} in one command:`,
203
+ );
204
+ lines.push("");
205
+ lines.push(` ${ANSI.cyan}failproofai policies --install ${installNames.join(" ")}${ANSI.reset}`);
206
+ lines.push("");
207
+ } else if (slippingRows.length > 0) {
208
+ lines.push(
209
+ `${ANSI.bold}NEXT${ANSI.reset} Everything blockable is already enabled. Audit-only findings will show up in your next ${ANSI.cyan}failproofai audit${ANSI.reset}.`,
210
+ );
211
+ lines.push("");
212
+ } else {
213
+ lines.push(`${ANSI.bold}NEXT${ANSI.reset} ${ANSI.green}You have the relevant policies enabled. failproofai is blocking these in real time.${ANSI.reset}`);
214
+ lines.push("");
215
+ }
216
+
217
+ // Mirror the actual --report path so the printed footer matches what was
218
+ // (or will be) written. Suppressed entirely with --no-report.
219
+ if (!opts.noReport) {
220
+ const reportPath = opts.reportPath ?? "./failproofai-audit.md";
221
+ lines.push(` 📄 Shareable report: ${ANSI.cyan}${reportPath}${ANSI.reset}`);
222
+ }
223
+ lines.push(` ⭐ Star us: ${ANSI.cyan}https://github.com/FailproofAI/failproofai${ANSI.reset}`);
224
+ lines.push("");
225
+
226
+ return noColorEnabled() ? stripAnsi(lines.join("\n")) : lines.join("\n");
227
+ }
228
+
229
+ export function formatJson(result: AuditResult): string {
230
+ return JSON.stringify(result, null, 2);
231
+ }
232
+
233
+ /** Escape characters that would break a markdown table row. Pipes split
234
+ * columns; backslashes escape the next char; leading newlines end the row. */
235
+ function escapeTableCell(s: string): string {
236
+ return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/[\r\n]+/g, " ");
237
+ }
238
+
239
+ function escapeBackticks(s: string): string {
240
+ return s.replace(/`/g, "\\`");
241
+ }
242
+
243
+ export function formatMarkdown(result: AuditResult): string {
244
+ const out: string[] = [];
245
+
246
+ const enabledRows = result.results.filter((r) => r.source === "builtin" && r.enabledInConfig);
247
+ const unenabledBuiltinRows = result.results.filter((r) => r.source === "builtin" && !r.enabledInConfig);
248
+ const detectorRows = result.results.filter((r) => r.source === "audit-detector");
249
+ const slippingRows = [...unenabledBuiltinRows, ...detectorRows].sort((a, b) => b.hits - a.hits);
250
+
251
+ const totalProtected = sumHits(enabledRows);
252
+ const totalSlipping = sumHits(slippingRows);
253
+ const totalHits = totalProtected + totalSlipping;
254
+ const sinceLabel = result.scope.since ? `last ${result.scope.since}` : "all time";
255
+
256
+ out.push(`# Agent behavior audit · ${sinceLabel}`);
257
+ out.push("");
258
+ out.push("> _Generated by `failproofai audit` (**beta**) — flags and output may change between releases. Going live shortly._");
259
+ out.push("");
260
+ out.push(`*Generated ${result.scannedAt} — scanned ${result.transcripts.scanned} sessions across ${result.totals.projectsWithHits} project(s) in ${(result.transcripts.durationMs / 1000).toFixed(1)}s.*`);
261
+ out.push("");
262
+
263
+ // TL;DR — readable to someone who doesn't know failproofai.
264
+ out.push("## TL;DR");
265
+ out.push("");
266
+ if (totalHits === 0) {
267
+ out.push("Clean run — the AI coding agent didn't do anything `failproofai` catches in this window.");
268
+ } else {
269
+ out.push(
270
+ `Over ${result.transcripts.scanned} sessions, my AI coding agent did **${totalHits} things \`failproofai\` would have stopped**: ` +
271
+ `${totalProtected} were already blocked in real time by my current config; ` +
272
+ `**${totalSlipping} slipped through** (would've been caught if more policies were on).`,
273
+ );
274
+ }
275
+ out.push("");
276
+ out.push("> [failproofai](https://github.com/FailproofAI/failproofai) is a hook-based policy engine for Claude Code, Codex, Copilot, Cursor, OpenCode, Pi, and Gemini CLI. The `audit` command replays past agent sessions through every builtin policy to surface patterns that were (or could've been) stopped.");
277
+ out.push("");
278
+
279
+ if (totalHits === 0) return out.join("\n");
280
+
281
+ // What was blocked
282
+ if (enabledRows.length > 0) {
283
+ out.push(`## ✓ Already protected (${totalProtected} action${totalProtected === 1 ? "" : "s"} stopped)`);
284
+ out.push("");
285
+ out.push("These are real-time policies you have on — `failproofai` blocked the agent before each action took effect.");
286
+ out.push("");
287
+ out.push("| Issue | Hits | Projects | Last seen | Policy |");
288
+ out.push("|---|---:|---:|---|---|");
289
+ for (const r of enabledRows) {
290
+ out.push(
291
+ `| ${escapeTableCell(r.displayTitle)} | ${r.hits} | ${r.projects} | ${formatTimeAgo(r.lastSeen)} | \`${escapeTableCell(shortName(r.name))}\` |`,
292
+ );
293
+ }
294
+ out.push("");
295
+ }
296
+
297
+ // What's slipping through
298
+ if (slippingRows.length > 0) {
299
+ out.push(`## ○ Slipping through (${totalSlipping} action${totalSlipping === 1 ? "" : "s"} caught by audit, not yet blocked)`);
300
+ out.push("");
301
+ out.push("Patterns the audit detected but real-time enforcement isn't on for. The CTA column shows how to fix each.");
302
+ out.push("");
303
+ out.push("| Issue | Hits | Projects | Last seen | Fix |");
304
+ out.push("|---|---:|---:|---|---|");
305
+ for (const r of slippingRows) {
306
+ const fix = r.source === "builtin"
307
+ ? `\`failproofai policies --install ${shortName(r.name)}\``
308
+ : "_audit-only_";
309
+ out.push(
310
+ `| ${escapeTableCell(r.displayTitle)} | ${r.hits} | ${r.projects} | ${formatTimeAgo(r.lastSeen)} | ${fix} |`,
311
+ );
312
+ }
313
+ out.push("");
314
+
315
+ if (unenabledBuiltinRows.length > 0) {
316
+ out.push(`### Enable everything in one command`);
317
+ out.push("");
318
+ out.push("```bash");
319
+ out.push(`failproofai policies --install ${unenabledBuiltinRows.map((r) => shortName(r.name)).join(" ")}`);
320
+ out.push("```");
321
+ out.push("");
322
+ }
323
+ }
324
+
325
+ // Examples appendix (only if non-trivial)
326
+ const rowsWithExamples = [...enabledRows, ...slippingRows].filter((r) => r.examples.length > 0);
327
+ if (rowsWithExamples.length > 0) {
328
+ out.push("## Examples");
329
+ out.push("");
330
+ for (const r of rowsWithExamples) {
331
+ out.push(`### ${escapeBackticks(r.displayTitle)} (\`${escapeBackticks(shortName(r.name))}\`)`);
332
+ out.push("");
333
+ if (r.impact) {
334
+ out.push(`> ${r.impact}`);
335
+ out.push("");
336
+ }
337
+ for (const e of r.examples) {
338
+ out.push(`- \`${escapeBackticks(e.example)}\` _(${e.cwd || "?"}, ${formatTimeAgo(e.timestamp)})_`);
339
+ }
340
+ out.push("");
341
+ }
342
+ }
343
+
344
+ out.push("---");
345
+ out.push("");
346
+ out.push("⭐ Star failproofai on GitHub: <https://github.com/FailproofAI/failproofai>");
347
+ out.push("");
348
+ return out.join("\n");
349
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * PostHog telemetry for `failproofai audit`.
3
+ *
4
+ * Plugs into the existing `trackHookEvent` surface (src/hooks/hook-telemetry.ts)
5
+ * — same distinct ID strategy, same opt-out (`FAILPROOFAI_TELEMETRY_DISABLED=1`),
6
+ * same fail-open contract (never crash the CLI).
7
+ *
8
+ * **Privacy contract — strictly enforced:**
9
+ * • Never send transcript paths, decoded project folder names, cwds,
10
+ * example command strings, file paths, sessionIds, or tool inputs.
11
+ * • Only ever send: policy/detector slugs (already public), counts,
12
+ * booleans, bucketed ages, CLI tags, output mode tags.
13
+ *
14
+ * Events fired during one `audit` run:
15
+ * 1. `audit_started` — once, before scanning
16
+ * 2. `audit_pattern_detected` — one per AuditCount aggregated
17
+ * 3. `audit_install_cta_shown` — once, if there are unenabled builtins
18
+ * 4. `audit_completed` — once, at the end
19
+ */
20
+ import { trackHookEvent } from "../hooks/hook-telemetry";
21
+ import { getInstanceId } from "../../lib/telemetry-id";
22
+ import type { AuditCount, AuditResult, RunAuditOptions } from "./types";
23
+
24
+ /** Bucketize an ISO timestamp into a coarse "days since" value to avoid
25
+ * shipping anything that could correlate with a specific session timing.
26
+ * Returns null when unknown. */
27
+ function ageBucketDays(iso: string | undefined): number | null {
28
+ if (!iso) return null;
29
+ const ms = Date.now() - new Date(iso).getTime();
30
+ if (Number.isNaN(ms) || ms < 0) return null;
31
+ const days = Math.floor(ms / 86_400_000);
32
+ // Bucket to nearest expected reporting horizon, never raw days
33
+ if (days <= 0) return 0;
34
+ if (days <= 1) return 1;
35
+ if (days <= 7) return 7;
36
+ if (days <= 30) return 30;
37
+ if (days <= 90) return 90;
38
+ if (days <= 365) return 365;
39
+ return 366;
40
+ }
41
+
42
+ function shortName(name: string): string {
43
+ const slash = name.indexOf("/");
44
+ return slash >= 0 ? name.slice(slash + 1) : name;
45
+ }
46
+
47
+ /** Fire-and-forget — telemetry never blocks or fails the CLI. Promises are
48
+ * awaited inside trackHookEvent (which has a 5s AbortSignal); callers can
49
+ * `void` the return. */
50
+ export function trackAuditStarted(opts: RunAuditOptions, outputMode: string): void {
51
+ void trackHookEvent(getInstanceId(), "audit_started", {
52
+ clis_requested: opts.clis ?? "all",
53
+ since_window: opts.since ?? null,
54
+ has_project_filter: !!opts.projects?.length,
55
+ has_policy_filter: !!opts.policies?.length,
56
+ no_cache: !!opts.noCache,
57
+ output_mode: outputMode,
58
+ });
59
+ }
60
+
61
+ export function trackAuditPatternDetected(count: AuditCount): void {
62
+ void trackHookEvent(getInstanceId(), "audit_pattern_detected", {
63
+ pattern_name: shortName(count.name),
64
+ pattern_source: count.source,
65
+ pattern_category: count.category,
66
+ hits: count.hits,
67
+ projects: count.projects,
68
+ enabled_in_config: count.enabledInConfig,
69
+ severity: count.severity,
70
+ first_seen_age_days: ageBucketDays(count.firstSeen),
71
+ last_seen_age_days: ageBucketDays(count.lastSeen),
72
+ });
73
+ }
74
+
75
+ export function trackAuditInstallCtaShown(unenabledNames: string[]): void {
76
+ if (unenabledNames.length === 0) return;
77
+ void trackHookEvent(getInstanceId(), "audit_install_cta_shown", {
78
+ unenabled_count: unenabledNames.length,
79
+ unenabled_pattern_names: unenabledNames.map(shortName),
80
+ });
81
+ }
82
+
83
+ export function trackAuditCompleted(
84
+ result: AuditResult,
85
+ outputMode: string,
86
+ ): void {
87
+ const enabledHits = result.results
88
+ .filter((r) => r.source === "builtin" && r.enabledInConfig)
89
+ .reduce((acc, r) => acc + r.hits, 0);
90
+ const unenabledHits = result.results
91
+ .filter((r) => r.source === "builtin" && !r.enabledInConfig)
92
+ .reduce((acc, r) => acc + r.hits, 0);
93
+ const detectorHits = result.results
94
+ .filter((r) => r.source === "audit-detector")
95
+ .reduce((acc, r) => acc + r.hits, 0);
96
+
97
+ void trackHookEvent(getInstanceId(), "audit_completed", {
98
+ duration_ms: result.transcripts.durationMs,
99
+ transcripts_scanned: result.transcripts.scanned,
100
+ transcripts_skipped: result.transcripts.skipped,
101
+ transcripts_errors: result.transcripts.errors,
102
+ total_hits: result.totals.hits,
103
+ projects_with_hits: result.totals.projectsWithHits,
104
+ enabled_builtin_hits: enabledHits,
105
+ unenabled_builtin_hits: unenabledHits,
106
+ audit_detector_hits: detectorHits,
107
+ result_count: result.results.length,
108
+ clis_scanned: result.scope.cli,
109
+ since_window: result.scope.since,
110
+ has_project_filter: result.scope.projects !== "all",
111
+ output_mode: outputMode,
112
+ });
113
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Types for the `failproofai audit` command.
3
+ *
4
+ * The audit walks past agent-CLI transcripts, replays each tool-use event
5
+ * through the existing policy engine, and runs each event through a separate
6
+ * set of audit-only detectors. Counts are aggregated per (policy|detector) and
7
+ * rendered as a table to stdout plus a markdown report.
8
+ */
9
+ import type { IntegrationType } from "../hooks/types";
10
+
11
+ /**
12
+ * A single tool-use event extracted from a transcript, canonicalized to the
13
+ * shape failproofai's policy engine + audit detectors expect.
14
+ */
15
+ export interface NormalizedToolEvent {
16
+ cli: IntegrationType;
17
+ sessionId: string;
18
+ transcriptPath: string;
19
+ /** Working directory of the session at the time of the event. */
20
+ cwd: string;
21
+ /** ISO-8601 timestamp from the transcript line. */
22
+ timestamp: string;
23
+ /** Canonical Claude PascalCase tool name (Bash, Read, Edit, …). */
24
+ toolName: string;
25
+ /** Canonicalized tool input (snake_case keys for OpenCode/Pi). */
26
+ toolInput: Record<string, unknown>;
27
+ /** Pre-canonicalization tool name (e.g. "run_shell_command" for Gemini). */
28
+ rawToolName: string;
29
+ /**
30
+ * Result text from the matching tool_result block, if present in the
31
+ * transcript. Used to synthesize PostToolUse events for sanitize-* policies.
32
+ * Truncated to AUDIT_TOOL_RESULT_MAX_BYTES at the adapter to bound memory.
33
+ */
34
+ toolResultText?: string;
35
+ }
36
+
37
+ /** Metadata about a single transcript (one session's JSONL file). */
38
+ export interface TranscriptMetadata {
39
+ cli: IntegrationType;
40
+ /** Encoded project folder name (e.g. "-home-user-project"). */
41
+ projectName: string;
42
+ sessionId: string;
43
+ transcriptPath: string;
44
+ /** mtime of the transcript file. Used for cache invalidation + --since filtering. */
45
+ mtimeMs: number;
46
+ /** Byte size of the transcript file. Used for cache invalidation. */
47
+ sizeBytes: number;
48
+ }
49
+
50
+ /** Per-session detector state. Detectors mutate this freely. */
51
+ export type DetectorSessionState = Record<string, unknown>;
52
+
53
+ /** One detected occurrence of a "stupid behavior". */
54
+ export interface DetectorHit {
55
+ /** A short, one-line summary of what triggered the detector. Used as the
56
+ * example column in the table and the example list in the markdown report.
57
+ * Truncated to 80 chars upstream. */
58
+ example: string;
59
+ }
60
+
61
+ /** A pure function over a normalized event (plus optional per-session state).
62
+ * Returns a DetectorHit when the event matches, null otherwise. */
63
+ export type DetectorFn = (
64
+ event: NormalizedToolEvent,
65
+ state: DetectorSessionState,
66
+ ) => DetectorHit | null;
67
+
68
+ /** Audit-only detector definition. */
69
+ export interface Detector {
70
+ name: string;
71
+ description: string;
72
+ /** Display group in the markdown report (Wasteful / Risky / …). */
73
+ category: string;
74
+ /** Severity tier — drives row color in the table. */
75
+ severity: "warn" | "info";
76
+ detect: DetectorFn;
77
+ /** User-facing past-tense phrase, e.g. "Re-read a file just edited". Falls
78
+ * back to `description` when omitted. */
79
+ displayTitle?: string;
80
+ /** One short clause describing the consequence. */
81
+ impact?: string;
82
+ }
83
+
84
+ /** Aggregated count for one policy or detector. */
85
+ export interface AuditCount {
86
+ /** Either a policy name ("failproofai/block-force-push") or a detector name
87
+ * ("redundant-cd-cwd"). */
88
+ name: string;
89
+ /** "builtin" for replayed policies, "audit-detector" for audit-only patterns. */
90
+ source: "builtin" | "audit-detector";
91
+ category: string;
92
+ /** Decision label for builtin replays (deny|instruct|allow|sanitize). For
93
+ * audit-detector entries this is the detector's severity. */
94
+ severity: string;
95
+ hits: number;
96
+ /** Number of distinct projects (transcript folders) where this fired. */
97
+ projects: number;
98
+ /** ISO-8601 timestamp of the first matching event seen. */
99
+ firstSeen?: string;
100
+ /** ISO-8601 timestamp of the last matching event seen. */
101
+ lastSeen?: string;
102
+ /** Up to N example commands/snippets that triggered this (80 chars each). */
103
+ examples: { sessionId: string; cwd: string; timestamp: string; example: string }[];
104
+ /** User-facing past-tense phrase used in the report. Always populated by the
105
+ * orchestrator — falls back to the policy/detector description when no
106
+ * `displayTitle` was authored. */
107
+ displayTitle: string;
108
+ /** One short clause describing the consequence. May be empty for legacy
109
+ * policies without authored impact copy. */
110
+ impact: string;
111
+ /** Whether the user currently has this builtin enabled. Drives the
112
+ * "already protected" vs "slipping through" split in the report. Always
113
+ * `false` for audit-only detectors (they don't have a runtime enforcement
114
+ * path today). */
115
+ enabledInConfig: boolean;
116
+ /** Pre-built one-line install command (or audit-only notice) the report
117
+ * shows next to each row, so users can copy-paste to remediate. */
118
+ installHint: string;
119
+ }
120
+
121
+ /** Per-transcript scan result (also the per-transcript cache value). */
122
+ export interface TranscriptAuditResult {
123
+ /** Cache key: matches TranscriptMetadata.transcriptPath. */
124
+ transcriptPath: string;
125
+ cli: IntegrationType;
126
+ projectName: string;
127
+ sessionId: string;
128
+ mtimeMs: number;
129
+ sizeBytes: number;
130
+ /** Per-policy/detector hit count for this one transcript. */
131
+ hitsByName: Record<string, number>;
132
+ /** Up to 3 example commands per policy/detector (later coalesced upstream). */
133
+ examplesByName: Record<string, { timestamp: string; cwd: string; example: string }[]>;
134
+ /** First/last timestamp per name. */
135
+ rangeByName: Record<string, { first: string; last: string }>;
136
+ }
137
+
138
+ /** Top-level result of `runAudit()`. */
139
+ export interface AuditResult {
140
+ /** Schema version of this JSON shape. Increment on incompatible changes. */
141
+ version: number;
142
+ scannedAt: string;
143
+ scope: {
144
+ cli: IntegrationType[];
145
+ projects: string[] | "all";
146
+ since: string | null;
147
+ };
148
+ transcripts: {
149
+ scanned: number;
150
+ skipped: number;
151
+ errors: number;
152
+ durationMs: number;
153
+ };
154
+ results: AuditCount[];
155
+ totals: {
156
+ hits: number;
157
+ projectsWithHits: number;
158
+ };
159
+ }
160
+
161
+ /** CLI-supplied options for `runAudit()`. Set by `bin/failproofai.mjs`. */
162
+ export interface RunAuditOptions {
163
+ /** Restrict to one or more CLIs. Default: all 7. */
164
+ clis?: IntegrationType[];
165
+ /** Restrict to sessions whose cwd matches one of these paths. */
166
+ projects?: string[];
167
+ /** "7d", "30d", or an ISO date. Filters on transcript mtime. */
168
+ since?: string;
169
+ /** Restrict to one or more policy/detector names. */
170
+ policies?: string[];
171
+ /** Top-N rows in the table output. */
172
+ limit?: number;
173
+ /** Include example column in the table. */
174
+ showExamples?: boolean;
175
+ /** Output path for the markdown report. Default: ./failproofai-audit.md */
176
+ reportPath?: string;
177
+ /** Skip writing the markdown report. */
178
+ noReport?: boolean;
179
+ /** Emit JSON to stdout instead of the table. */
180
+ json?: boolean;
181
+ /** Bypass the per-transcript cache. */
182
+ noCache?: boolean;
183
+ }
184
+
185
+ /** Truncation budget for `NormalizedToolEvent.toolResultText`. Bounds memory
186
+ * for sanitize-* policy replay without losing the typical match window. */
187
+ export const AUDIT_TOOL_RESULT_MAX_BYTES = 64 * 1024;
188
+
189
+ /** Truncation budget for example strings shown in table + markdown. */
190
+ export const AUDIT_EXAMPLE_MAX_CHARS = 80;
191
+
192
+ /** Max examples kept per policy/detector in the final report. */
193
+ export const AUDIT_MAX_EXAMPLES_PER_NAME = 3;