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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +7 -7
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/page/next-font-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +4 -4
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/policies/page/next-font-manifest.json +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +4 -4
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/project/[name]/page/next-font-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +4 -4
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/next-font-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -4
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/projects/page/next-font-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +4 -4
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_ob4n._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__044xt9.._.js → [root-of-the-server]__0fwb7ao._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g48iv.._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0j8-xkl._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0bdfoky.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_05pz9._._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wn51s._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01as125._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__098zro9._.js +19 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__02r.cjq._.js → [root-of-the-server]__09v.ljl._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0agrcb8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0b7hkr~._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ehh6vp._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g8l0tu._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0j4l6hl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ye1w50._.js → [root-of-the-server]__0k5n2kz._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lp08ll._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0yaqw._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0o21f.o._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t8juvy._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__10xgshr._.js → [root-of-the-server]__0tcyn68._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ts150~._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0podumr._.js → [root-of-the-server]__0uylufv._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +5 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0~03grs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_0cdqd9w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_utils_ts_068jk73._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_0ttbz1~._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_06u0kr8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0h9llsw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0a_7sdg.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0ef3uwk.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0j79~gv.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0pbja1x.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0r6o0i2.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_11y81~_.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_12or2kf.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js +1 -1
- package/.next/standalone/.next/server/functions-config-manifest.json +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +7 -7
- package/.next/standalone/.next/server/next-font-manifest.js +1 -1
- package/.next/standalone/.next/server/next-font-manifest.json +6 -6
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/07kpqoo7kuckx.js +6 -0
- package/.next/standalone/.next/static/chunks/0a40sy4tk8ioe.js +1 -0
- package/.next/standalone/.next/static/chunks/{12l2t63hkyo2q.js → 0azb~vy9ds_uy.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0j171xiqge4rv.js → 0bke.~atnsbeb.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0lt8ko3lw.5yt.js → 0bv1oyxspkpkb.js} +1 -1
- package/.next/standalone/.next/static/chunks/{179yytvmam0ug.js → 0dvhi-prcsh3~.js} +1 -1
- package/.next/standalone/.next/static/chunks/0f5p9plm.aqlp.css +2 -0
- package/.next/standalone/.next/static/chunks/0ffvlbgzgnlw7.js +2 -0
- package/.next/standalone/.next/static/chunks/{150i0n26fnvso.js → 0n1n67imq.udf.js} +1 -1
- package/.next/standalone/.next/static/chunks/0spktq7xqab9h.js +1 -0
- package/.next/standalone/.next/static/chunks/{14lii11wmo450.js → 118q3uljozd5z.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0pkl..xgo-qox.js → 11w14gnqzprir.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0rnqmir4cd5p9.js → 17mubwtqwijpu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-05z7a19q43zfq.js → turbopack-0nh.aopesgj~5.js} +1 -1
- package/.next/standalone/.next/static/media/4fa387ec64143e14-s.0.qu-9752pffj.woff2 +0 -0
- package/.next/standalone/.next/static/media/5ce348bf30bf5439-s.0ee55_hj9qcer.woff2 +0 -0
- package/.next/standalone/.next/static/media/6306c77e7c8268e4-s.0mao5jbfbduzp.woff2 +0 -0
- package/.next/standalone/.next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2 +0 -0
- package/.next/standalone/.next/static/media/7d817b4c03b0c5f1-s.0uzt.a6d44yda.woff2 +0 -0
- package/.next/standalone/.next/static/media/bbc41e54d2fcbd21-s.0mvwgmnhv29no.woff2 +0 -0
- package/.next/standalone/.next/static/{dAuQps6jUwCz9X1Q5FFOO → tGVQM5SE3NvbVu0gbAJm7}/_clientMiddlewareManifest.js +2 -2
- package/.next/standalone/app/policies/hooks-client.tsx +111 -14
- package/.next/standalone/components/navbar.tsx +1 -1
- package/.next/standalone/components/reach-developers.tsx +2 -2
- package/.next/standalone/lib/claude-sessions.ts +181 -0
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/static-paths/app.js +2 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/build/utils.js +2 -1
- package/.next/standalone/node_modules/next/dist/client/components/router-reducer/fetch-server-response.js +2 -2
- package/.next/standalone/node_modules/next/dist/client/components/router-reducer/set-cache-busting-search-param.js +8 -2
- package/.next/standalone/node_modules/next/dist/client/route-params.js +23 -6
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +13 -13
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +11 -11
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +2 -2
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +10 -10
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/app-render/action-handler.js +3 -6
- package/.next/standalone/node_modules/next/dist/server/app-render/app-render.js +62 -9
- package/.next/standalone/node_modules/next/dist/server/app-render/collect-segment-data.js +16 -0
- package/.next/standalone/node_modules/next/dist/server/app-render/create-component-tree.js +49 -19
- package/.next/standalone/node_modules/next/dist/server/app-render/get-script-nonce-from-header.js +8 -20
- package/.next/standalone/node_modules/next/dist/server/app-render/metadata-insertion/create-server-inserted-metadata.js +8 -7
- package/.next/standalone/node_modules/next/dist/server/app-render/use-flight-response.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/async-storage/work-store.js +2 -1
- package/.next/standalone/node_modules/next/dist/server/base-server.js +13 -5
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/static-paths-worker.js +2 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +22 -2
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/is-rsc-request.js +18 -0
- package/.next/standalone/node_modules/next/dist/server/lib/mock-request.js +30 -5
- package/.next/standalone/node_modules/next/dist/server/lib/patch-set-header.js +7 -0
- package/.next/standalone/node_modules/next/dist/server/lib/router-server.js +6 -3
- package/.next/standalone/node_modules/next/dist/server/lib/router-utils/resolve-routes.js +18 -4
- package/.next/standalone/node_modules/next/dist/server/lib/server-ipc/utils.js +3 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/next-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/request/fallback-params.js +27 -1
- package/.next/standalone/node_modules/next/dist/server/route-modules/app-route/module.js +1 -0
- package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +11 -1
- package/.next/standalone/node_modules/next/dist/server/server-utils.js +19 -2
- package/.next/standalone/node_modules/next/dist/server/stream-utils/node-web-streams-helper.js +5 -5
- package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/web/adapter.js +4 -1
- package/.next/standalone/node_modules/next/dist/server/web/edge-route-module-wrapper.js +2 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
- package/.next/standalone/node_modules/next/dist/{server → shared/lib}/htmlescape.js +15 -0
- package/.next/standalone/node_modules/next/dist/shared/lib/router/routes/app.js +13 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/cache-busting-search-param.js +56 -10
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +15 -15
- package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
- package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
- package/.next/standalone/node_modules/react/package.json +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/package.json +2 -2
- package/.next/standalone/package.json +5 -5
- package/.next/standalone/proxy.ts +1 -1
- package/.next/standalone/server.js +1 -1
- package/README.md +4 -4
- package/bin/failproofai.mjs +230 -73
- package/dist/cli.mjs +3028 -1453
- package/lib/claude-sessions.ts +181 -0
- package/package.json +5 -5
- package/scripts/launch.ts +1 -1
- package/scripts/postinstall.mjs +89 -1
- package/src/audit/cache.ts +113 -0
- package/src/audit/cli-adapters/claude.ts +97 -0
- package/src/audit/cli-adapters/codex.ts +56 -0
- package/src/audit/cli-adapters/copilot.ts +51 -0
- package/src/audit/cli-adapters/cursor.ts +51 -0
- package/src/audit/cli-adapters/gemini.ts +51 -0
- package/src/audit/cli-adapters/index.ts +70 -0
- package/src/audit/cli-adapters/opencode.ts +52 -0
- package/src/audit/cli-adapters/pi.ts +51 -0
- package/src/audit/cli-adapters/shared.ts +85 -0
- package/src/audit/detectors/find-from-root.ts +27 -0
- package/src/audit/detectors/git-commit-no-verify.ts +22 -0
- package/src/audit/detectors/index.ts +33 -0
- package/src/audit/detectors/prefer-edit-over-read-cat.ts +31 -0
- package/src/audit/detectors/prefer-edit-over-sed-awk.ts +27 -0
- package/src/audit/detectors/prefer-write-over-heredoc.ts +36 -0
- package/src/audit/detectors/redundant-cd-cwd.ts +28 -0
- package/src/audit/detectors/reread-after-edit.ts +58 -0
- package/src/audit/detectors/sleep-polling-loop.ts +34 -0
- package/src/audit/index.ts +369 -0
- package/src/audit/replay.ts +121 -0
- package/src/audit/report.ts +349 -0
- package/src/audit/telemetry.ts +113 -0
- package/src/audit/types.ts +193 -0
- package/src/hooks/builtin-policies.ts +79 -1
- package/src/hooks/custom-hooks-loader.ts +19 -3
- package/src/hooks/first-run-nudge.ts +146 -0
- package/src/hooks/handler.ts +21 -102
- package/src/hooks/install-prompt.ts +34 -4
- package/src/hooks/manager.ts +72 -5
- package/src/hooks/policy-evaluator.ts +19 -4
- package/src/hooks/policy-registry.ts +1 -1
- package/src/hooks/policy-types.ts +9 -0
- package/src/hooks/tool-name-canonicalize.ts +65 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0609ezh._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__07_-mkc._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09z7o2x._.js +0 -19
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_sh2n0._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e9o9ri._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0l6swv1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0logebz._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mi5ejy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0odijkc._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rkxer-._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rl2kwi._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vg0uey._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0x5limi._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10._f0s._.js +0 -4
- package/.next/standalone/.next/static/chunks/01q52wg_amm60.js +0 -2
- package/.next/standalone/.next/static/chunks/0kqar56yl~41o.js +0 -6
- package/.next/standalone/.next/static/chunks/0ml1.ck_5t36i.js +0 -1
- package/.next/standalone/.next/static/chunks/0zig0fh30t6ou.js +0 -1
- package/.next/standalone/.next/static/chunks/17rm86uz2nd5a.css +0 -2
- package/.next/standalone/.next/static/media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2 +0 -0
- package/.next/standalone/.next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2 +0 -0
- package/.next/standalone/.next/static/media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2 +0 -0
- package/src/auth/login.ts +0 -104
- package/src/auth/logout.ts +0 -50
- package/src/auth/token-store.ts +0 -64
- package/src/relay/daemon.ts +0 -362
- package/src/relay/pid.ts +0 -76
- package/src/relay/queue.ts +0 -225
- /package/.next/standalone/.next/static/{dAuQps6jUwCz9X1Q5FFOO → tGVQM5SE3NvbVu0gbAJm7}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{dAuQps6jUwCz9X1Q5FFOO → tGVQM5SE3NvbVu0gbAJm7}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runAudit` — entry point for the `failproofai audit` command.
|
|
3
|
+
*
|
|
4
|
+
* Drives the pipeline: per-CLI adapters → tool events → (replay + audit
|
|
5
|
+
* detectors) → per-transcript results → aggregated `AuditResult`.
|
|
6
|
+
*
|
|
7
|
+
* Parallelizes transcript scans via `lib/concurrency.ts` `batchAll` (8 at a
|
|
8
|
+
* time — bounds disk I/O without overwhelming the policy engine).
|
|
9
|
+
*/
|
|
10
|
+
import { batchAll } from "../../lib/concurrency";
|
|
11
|
+
import { BUILTIN_POLICIES } from "../hooks/builtin-policies";
|
|
12
|
+
import { readMergedHooksConfig } from "../hooks/hooks-config";
|
|
13
|
+
import { normalizePolicyName } from "../hooks/policy-registry";
|
|
14
|
+
import { INTEGRATION_TYPES, type IntegrationType } from "../hooks/types";
|
|
15
|
+
import { ADAPTERS } from "./cli-adapters";
|
|
16
|
+
import { AUDIT_DETECTORS } from "./detectors";
|
|
17
|
+
import { readCachedTranscriptResult, writeCachedTranscriptResult } from "./cache";
|
|
18
|
+
import { initReplay, replayEvent } from "./replay";
|
|
19
|
+
import {
|
|
20
|
+
trackAuditCompleted,
|
|
21
|
+
trackAuditInstallCtaShown,
|
|
22
|
+
trackAuditPatternDetected,
|
|
23
|
+
trackAuditStarted,
|
|
24
|
+
} from "./telemetry";
|
|
25
|
+
import {
|
|
26
|
+
AUDIT_EXAMPLE_MAX_CHARS,
|
|
27
|
+
AUDIT_MAX_EXAMPLES_PER_NAME,
|
|
28
|
+
type AuditCount,
|
|
29
|
+
type AuditResult,
|
|
30
|
+
type DetectorSessionState,
|
|
31
|
+
type NormalizedToolEvent,
|
|
32
|
+
type RunAuditOptions,
|
|
33
|
+
type TranscriptAuditResult,
|
|
34
|
+
type TranscriptMetadata,
|
|
35
|
+
} from "./types";
|
|
36
|
+
|
|
37
|
+
const TRANSCRIPT_CONCURRENCY = 8;
|
|
38
|
+
|
|
39
|
+
/** Canonicalize a policy name to its short, qualified form for display
|
|
40
|
+
* (`failproofai/foo` → `foo`). */
|
|
41
|
+
function shortPolicyName(name: string): string {
|
|
42
|
+
const slash = name.indexOf("/");
|
|
43
|
+
return slash >= 0 ? name.slice(slash + 1) : name;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Look up a builtin policy definition by canonical name; null when the name
|
|
47
|
+
* doesn't match a builtin (e.g. user custom policy). */
|
|
48
|
+
function findBuiltin(name: string) {
|
|
49
|
+
const short = shortPolicyName(name);
|
|
50
|
+
for (const p of BUILTIN_POLICIES) {
|
|
51
|
+
if (p.name === name || shortPolicyName(p.name) === short) return p;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Build the per-row install hint shown in the report:
|
|
57
|
+
* - Already enabled builtin: a check phrase ("Already enforced — currently blocking these in real time")
|
|
58
|
+
* - Unenabled builtin: `failproofai policies --install <short-name>`
|
|
59
|
+
* - Audit-only detector: soft notice ("Audit-only — `failproofai audit` will keep tracking these")
|
|
60
|
+
* - Unknown / custom: empty string
|
|
61
|
+
*/
|
|
62
|
+
function buildInstallHint(
|
|
63
|
+
name: string,
|
|
64
|
+
source: "builtin" | "audit-detector",
|
|
65
|
+
enabled: boolean,
|
|
66
|
+
): string {
|
|
67
|
+
if (source === "audit-detector") {
|
|
68
|
+
return "Audit-only — `failproofai audit` will keep tracking these.";
|
|
69
|
+
}
|
|
70
|
+
if (enabled) {
|
|
71
|
+
return "Already enforced — failproofai is blocking these in real time.";
|
|
72
|
+
}
|
|
73
|
+
return `Enable in one command: failproofai policies --install ${shortPolicyName(name)}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function truncateExample(s: string): string {
|
|
77
|
+
if (s.length <= AUDIT_EXAMPLE_MAX_CHARS) return s;
|
|
78
|
+
return s.slice(0, AUDIT_EXAMPLE_MAX_CHARS - 1) + "…";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseSinceOpt(since: string | undefined): number | undefined {
|
|
82
|
+
if (!since) return undefined;
|
|
83
|
+
const m = /^(\d+)\s*([dhm])$/i.exec(since.trim());
|
|
84
|
+
if (m) {
|
|
85
|
+
const n = parseInt(m[1], 10);
|
|
86
|
+
const unit = m[2].toLowerCase();
|
|
87
|
+
const ms = unit === "d" ? 86400000 : unit === "h" ? 3600000 : 60000;
|
|
88
|
+
return Date.now() - n * ms;
|
|
89
|
+
}
|
|
90
|
+
const t = Date.parse(since);
|
|
91
|
+
if (!Number.isNaN(t)) return t;
|
|
92
|
+
throw new Error(`Invalid --since value: "${since}" (expected e.g. "7d", "30d", or "2026-04-01")`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function scanOneTranscript(meta: TranscriptMetadata): Promise<TranscriptAuditResult> {
|
|
96
|
+
const empty: TranscriptAuditResult = {
|
|
97
|
+
transcriptPath: meta.transcriptPath,
|
|
98
|
+
cli: meta.cli,
|
|
99
|
+
projectName: meta.projectName,
|
|
100
|
+
sessionId: meta.sessionId,
|
|
101
|
+
mtimeMs: meta.mtimeMs,
|
|
102
|
+
sizeBytes: meta.sizeBytes,
|
|
103
|
+
hitsByName: {},
|
|
104
|
+
examplesByName: {},
|
|
105
|
+
rangeByName: {},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Stream failures must propagate so the orchestrator counts them in
|
|
109
|
+
// `errors` rather than silently returning an empty hits map.
|
|
110
|
+
const events = await ADAPTERS[meta.cli].streamEvents(meta);
|
|
111
|
+
if (events.length === 0) return empty;
|
|
112
|
+
|
|
113
|
+
const result = empty;
|
|
114
|
+
const sessionState: DetectorSessionState = {};
|
|
115
|
+
|
|
116
|
+
for (const event of events) {
|
|
117
|
+
// Run audit detectors first (stateful, must see every event).
|
|
118
|
+
for (const detector of AUDIT_DETECTORS) {
|
|
119
|
+
const hit = detector.detect(event, sessionState);
|
|
120
|
+
if (!hit) continue;
|
|
121
|
+
recordHit(
|
|
122
|
+
result,
|
|
123
|
+
detector.name,
|
|
124
|
+
event.timestamp,
|
|
125
|
+
event.cwd,
|
|
126
|
+
truncateExample(hit.example),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
// Then replay through every builtin policy.
|
|
130
|
+
let replayHits;
|
|
131
|
+
try {
|
|
132
|
+
replayHits = await replayEvent(event);
|
|
133
|
+
} catch {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
for (const hit of replayHits) {
|
|
137
|
+
const example = formatPolicyExample(hit.policyName, event);
|
|
138
|
+
recordHit(
|
|
139
|
+
result,
|
|
140
|
+
hit.policyName,
|
|
141
|
+
event.timestamp,
|
|
142
|
+
event.cwd,
|
|
143
|
+
truncateExample(example),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function formatPolicyExample(_policyName: string, event: NormalizedToolEvent): string {
|
|
152
|
+
if (event.toolName === "Bash") {
|
|
153
|
+
const command = (event.toolInput as { command?: unknown }).command;
|
|
154
|
+
if (typeof command === "string") return command.replace(/\s+/g, " ");
|
|
155
|
+
}
|
|
156
|
+
const filePath = (event.toolInput as { file_path?: unknown }).file_path;
|
|
157
|
+
if (typeof filePath === "string") return `${event.toolName} ${filePath}`;
|
|
158
|
+
return `${event.toolName}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function recordHit(
|
|
162
|
+
result: TranscriptAuditResult,
|
|
163
|
+
name: string,
|
|
164
|
+
timestamp: string,
|
|
165
|
+
cwd: string,
|
|
166
|
+
example: string,
|
|
167
|
+
): void {
|
|
168
|
+
result.hitsByName[name] = (result.hitsByName[name] ?? 0) + 1;
|
|
169
|
+
const exs = result.examplesByName[name] ?? [];
|
|
170
|
+
if (exs.length < AUDIT_MAX_EXAMPLES_PER_NAME) {
|
|
171
|
+
exs.push({ timestamp, cwd, example });
|
|
172
|
+
result.examplesByName[name] = exs;
|
|
173
|
+
}
|
|
174
|
+
const range = result.rangeByName[name];
|
|
175
|
+
if (!range) {
|
|
176
|
+
result.rangeByName[name] = { first: timestamp, last: timestamp };
|
|
177
|
+
} else {
|
|
178
|
+
if (timestamp < range.first) range.first = timestamp;
|
|
179
|
+
if (timestamp > range.last) range.last = timestamp;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function aggregateResults(
|
|
184
|
+
perTranscript: TranscriptAuditResult[],
|
|
185
|
+
enabledBuiltins: Set<string>,
|
|
186
|
+
): AuditCount[] {
|
|
187
|
+
// For each name: sum hits, count distinct projects, merge ranges + examples.
|
|
188
|
+
const byName = new Map<string, {
|
|
189
|
+
hits: number;
|
|
190
|
+
projects: Set<string>;
|
|
191
|
+
examples: { sessionId: string; cwd: string; timestamp: string; example: string }[];
|
|
192
|
+
first?: string;
|
|
193
|
+
last?: string;
|
|
194
|
+
}>();
|
|
195
|
+
|
|
196
|
+
for (const t of perTranscript) {
|
|
197
|
+
for (const [name, count] of Object.entries(t.hitsByName)) {
|
|
198
|
+
const bucket = byName.get(name) ?? {
|
|
199
|
+
hits: 0,
|
|
200
|
+
projects: new Set<string>(),
|
|
201
|
+
examples: [],
|
|
202
|
+
};
|
|
203
|
+
bucket.hits += count;
|
|
204
|
+
bucket.projects.add(t.projectName);
|
|
205
|
+
const tExs = t.examplesByName[name] ?? [];
|
|
206
|
+
for (const e of tExs) {
|
|
207
|
+
if (bucket.examples.length < AUDIT_MAX_EXAMPLES_PER_NAME) {
|
|
208
|
+
bucket.examples.push({ ...e, sessionId: t.sessionId });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const range = t.rangeByName[name];
|
|
212
|
+
if (range) {
|
|
213
|
+
if (!bucket.first || range.first < bucket.first) bucket.first = range.first;
|
|
214
|
+
if (!bucket.last || range.last > bucket.last) bucket.last = range.last;
|
|
215
|
+
}
|
|
216
|
+
byName.set(name, bucket);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const detectorByName = new Map(AUDIT_DETECTORS.map((d) => [d.name, d]));
|
|
221
|
+
const out: AuditCount[] = [];
|
|
222
|
+
for (const [name, bucket] of byName) {
|
|
223
|
+
const detector = detectorByName.get(name);
|
|
224
|
+
const isDetector = !!detector;
|
|
225
|
+
const builtin = isDetector ? null : findBuiltin(name);
|
|
226
|
+
const source: "builtin" | "audit-detector" = isDetector ? "audit-detector" : "builtin";
|
|
227
|
+
const enabled = isDetector ? false : enabledBuiltins.has(normalizePolicyName(name));
|
|
228
|
+
|
|
229
|
+
const displayTitle =
|
|
230
|
+
detector?.displayTitle
|
|
231
|
+
?? builtin?.displayTitle
|
|
232
|
+
?? detector?.description
|
|
233
|
+
?? builtin?.description
|
|
234
|
+
?? shortPolicyName(name);
|
|
235
|
+
const impact = detector?.impact ?? builtin?.impact ?? "";
|
|
236
|
+
|
|
237
|
+
out.push({
|
|
238
|
+
name,
|
|
239
|
+
source,
|
|
240
|
+
category: detector?.category ?? builtin?.category ?? "Custom",
|
|
241
|
+
severity: isDetector ? (detector?.severity ?? "info") : "deny",
|
|
242
|
+
hits: bucket.hits,
|
|
243
|
+
projects: bucket.projects.size,
|
|
244
|
+
firstSeen: bucket.first,
|
|
245
|
+
lastSeen: bucket.last,
|
|
246
|
+
examples: bucket.examples,
|
|
247
|
+
displayTitle,
|
|
248
|
+
impact,
|
|
249
|
+
enabledInConfig: enabled,
|
|
250
|
+
installHint: buildInstallHint(name, source, enabled),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
out.sort((a, b) => b.hits - a.hits);
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function runAudit(opts: RunAuditOptions = {}): Promise<AuditResult> {
|
|
259
|
+
const startedAt = Date.now();
|
|
260
|
+
initReplay();
|
|
261
|
+
|
|
262
|
+
const outputMode = opts.json ? "json" : opts.noReport ? "text" : "text+markdown";
|
|
263
|
+
trackAuditStarted(opts, outputMode);
|
|
264
|
+
|
|
265
|
+
const clis = (opts.clis ?? Array.from(INTEGRATION_TYPES)) as IntegrationType[];
|
|
266
|
+
const sinceMs = parseSinceOpt(opts.since);
|
|
267
|
+
|
|
268
|
+
// Snapshot which builtin policies the user currently has enabled — drives
|
|
269
|
+
// the "already protected" vs "slipping through" split in the report.
|
|
270
|
+
const userConfig = readMergedHooksConfig();
|
|
271
|
+
const enabledBuiltins = new Set(
|
|
272
|
+
(userConfig.enabledPolicies ?? []).map((n) => normalizePolicyName(n)),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// 1. Discover transcripts across all selected CLIs.
|
|
276
|
+
const allTranscripts: TranscriptMetadata[] = [];
|
|
277
|
+
for (const cli of clis) {
|
|
278
|
+
const adapter = ADAPTERS[cli];
|
|
279
|
+
let list: TranscriptMetadata[];
|
|
280
|
+
try {
|
|
281
|
+
list = await adapter.listTranscripts({ projects: opts.projects, sinceMs });
|
|
282
|
+
} catch {
|
|
283
|
+
continue; // adapter failures shouldn't kill the whole audit
|
|
284
|
+
}
|
|
285
|
+
allTranscripts.push(...list);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 2. Scan each transcript (cache-aware), 8 in parallel.
|
|
289
|
+
let skipped = 0;
|
|
290
|
+
let errors = 0;
|
|
291
|
+
const tasks = allTranscripts.map((meta) => async (): Promise<TranscriptAuditResult> => {
|
|
292
|
+
if (!opts.noCache) {
|
|
293
|
+
const cached = readCachedTranscriptResult(meta.transcriptPath, meta.mtimeMs, meta.sizeBytes);
|
|
294
|
+
if (cached) return cached;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const fresh = await scanOneTranscript(meta);
|
|
298
|
+
if (!opts.noCache) {
|
|
299
|
+
writeCachedTranscriptResult(meta.transcriptPath, meta.mtimeMs, meta.sizeBytes, fresh);
|
|
300
|
+
}
|
|
301
|
+
return fresh;
|
|
302
|
+
} catch {
|
|
303
|
+
errors++;
|
|
304
|
+
return {
|
|
305
|
+
transcriptPath: meta.transcriptPath,
|
|
306
|
+
cli: meta.cli,
|
|
307
|
+
projectName: meta.projectName,
|
|
308
|
+
sessionId: meta.sessionId,
|
|
309
|
+
mtimeMs: meta.mtimeMs,
|
|
310
|
+
sizeBytes: meta.sizeBytes,
|
|
311
|
+
hitsByName: {},
|
|
312
|
+
examplesByName: {},
|
|
313
|
+
rangeByName: {},
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const settled = await batchAll(tasks, TRANSCRIPT_CONCURRENCY);
|
|
319
|
+
const perTranscript: TranscriptAuditResult[] = [];
|
|
320
|
+
for (const s of settled) {
|
|
321
|
+
if (s.status === "fulfilled") perTranscript.push(s.value);
|
|
322
|
+
else skipped++;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 3. Aggregate.
|
|
326
|
+
let results = aggregateResults(perTranscript, enabledBuiltins);
|
|
327
|
+
if (opts.policies?.length) {
|
|
328
|
+
const wanted = new Set(opts.policies.map(shortPolicyName));
|
|
329
|
+
results = results.filter((r) => wanted.has(shortPolicyName(r.name)));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const totalsHits = results.reduce((sum, r) => sum + r.hits, 0);
|
|
333
|
+
const projectsWithHits = new Set<string>();
|
|
334
|
+
for (const t of perTranscript) {
|
|
335
|
+
if (Object.keys(t.hitsByName).length > 0) projectsWithHits.add(t.projectName);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const auditResult: AuditResult = {
|
|
339
|
+
version: 1,
|
|
340
|
+
scannedAt: new Date(startedAt).toISOString(),
|
|
341
|
+
scope: {
|
|
342
|
+
cli: clis,
|
|
343
|
+
projects: opts.projects ?? "all",
|
|
344
|
+
since: opts.since ?? null,
|
|
345
|
+
},
|
|
346
|
+
transcripts: {
|
|
347
|
+
scanned: allTranscripts.length,
|
|
348
|
+
skipped,
|
|
349
|
+
errors,
|
|
350
|
+
durationMs: Date.now() - startedAt,
|
|
351
|
+
},
|
|
352
|
+
results,
|
|
353
|
+
totals: {
|
|
354
|
+
hits: totalsHits,
|
|
355
|
+
projectsWithHits: projectsWithHits.size,
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Telemetry — fire-and-forget, never blocks the CLI. See src/audit/telemetry.ts
|
|
360
|
+
// for the privacy contract (slugs + counts + booleans only).
|
|
361
|
+
for (const count of results) trackAuditPatternDetected(count);
|
|
362
|
+
const unenabledBuiltinNames = results
|
|
363
|
+
.filter((r) => r.source === "builtin" && !r.enabledInConfig)
|
|
364
|
+
.map((r) => r.name);
|
|
365
|
+
trackAuditInstallCtaShown(unenabledBuiltinNames);
|
|
366
|
+
trackAuditCompleted(auditResult, outputMode);
|
|
367
|
+
|
|
368
|
+
return auditResult;
|
|
369
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replay engine — turns a NormalizedToolEvent into one (or two) synthetic hook
|
|
3
|
+
* payloads, runs them through the existing `evaluatePolicies` function, and
|
|
4
|
+
* returns the policy decisions.
|
|
5
|
+
*
|
|
6
|
+
* Why two events: builtin sanitize-* policies match on PostToolUse and inspect
|
|
7
|
+
* the tool result text. PreToolUse alone misses them. Per event we synthesize:
|
|
8
|
+
* 1. PreToolUse — { tool_name, tool_input }
|
|
9
|
+
* 2. PostToolUse — { tool_name, tool_input, tool_response } (only when the
|
|
10
|
+
* transcript captured a tool_result)
|
|
11
|
+
*
|
|
12
|
+
* Workflow policies (`require-*-before-stop`) match only on `Stop` and
|
|
13
|
+
* execSync against live git, so they never fire on PreToolUse/PostToolUse
|
|
14
|
+
* replay — no explicit skip needed.
|
|
15
|
+
*/
|
|
16
|
+
import type { EvaluationResult } from "../hooks/policy-evaluator";
|
|
17
|
+
import { evaluatePolicies } from "../hooks/policy-evaluator";
|
|
18
|
+
import { BUILTIN_POLICIES, registerBuiltinPolicies } from "../hooks/builtin-policies";
|
|
19
|
+
import { clearPolicies, normalizePolicyName } from "../hooks/policy-registry";
|
|
20
|
+
import type { SessionMetadata } from "../hooks/types";
|
|
21
|
+
import type { NormalizedToolEvent } from "./types";
|
|
22
|
+
|
|
23
|
+
/** Policies the audit skips on purpose. `warn-repeated-tool-calls` mutates a
|
|
24
|
+
* per-session sidecar file on every evaluation, which would pollute the
|
|
25
|
+
* user's real transcript directory during a replay (and inflate counts
|
|
26
|
+
* because the replay always re-traverses the full session). */
|
|
27
|
+
const SKIP_POLICIES = new Set(
|
|
28
|
+
["warn-repeated-tool-calls"].map((n) => normalizePolicyName(n)),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
let initialized = false;
|
|
32
|
+
|
|
33
|
+
/** Register every builtin policy (regardless of user config) so the replay
|
|
34
|
+
* shows what *could* be caught, not just what's currently enabled. Called
|
|
35
|
+
* once per `runAudit` invocation. */
|
|
36
|
+
export function initReplay(): void {
|
|
37
|
+
if (initialized) return;
|
|
38
|
+
clearPolicies();
|
|
39
|
+
const enabled = BUILTIN_POLICIES
|
|
40
|
+
.map((p) => p.name)
|
|
41
|
+
.filter((n) => !SKIP_POLICIES.has(normalizePolicyName(n)));
|
|
42
|
+
registerBuiltinPolicies(enabled);
|
|
43
|
+
initialized = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Reset for tests / repeated audits in the same process. */
|
|
47
|
+
export function resetReplay(): void {
|
|
48
|
+
initialized = false;
|
|
49
|
+
clearPolicies();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ReplayHit {
|
|
53
|
+
policyName: string;
|
|
54
|
+
decision: "deny" | "instruct" | "allow";
|
|
55
|
+
reason: string | null;
|
|
56
|
+
eventType: "PreToolUse" | "PostToolUse";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Replay one normalized tool event through every registered policy. Returns
|
|
60
|
+
* one ReplayHit per non-allow decision (deny + instruct). Allow-with-reason
|
|
61
|
+
* is reported too, so sanitize policies that emit informational notes still
|
|
62
|
+
* surface in the audit. */
|
|
63
|
+
export async function replayEvent(event: NormalizedToolEvent): Promise<ReplayHit[]> {
|
|
64
|
+
if (!initialized) initReplay();
|
|
65
|
+
|
|
66
|
+
const session: SessionMetadata = {
|
|
67
|
+
sessionId: event.sessionId,
|
|
68
|
+
transcriptPath: event.transcriptPath,
|
|
69
|
+
cwd: event.cwd,
|
|
70
|
+
cli: event.cli,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const baseToolPayload: Record<string, unknown> = {
|
|
74
|
+
tool_name: event.toolName,
|
|
75
|
+
tool_input: event.toolInput,
|
|
76
|
+
session_id: event.sessionId,
|
|
77
|
+
cwd: event.cwd,
|
|
78
|
+
transcript_path: event.transcriptPath,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const out: ReplayHit[] = [];
|
|
82
|
+
|
|
83
|
+
// PreToolUse
|
|
84
|
+
const pre = await evaluatePolicies("PreToolUse", baseToolPayload, session);
|
|
85
|
+
collectHits(pre, "PreToolUse", out);
|
|
86
|
+
|
|
87
|
+
// PostToolUse — only if the transcript captured a tool result.
|
|
88
|
+
if (event.toolResultText !== undefined) {
|
|
89
|
+
const postPayload = { ...baseToolPayload, tool_response: event.toolResultText };
|
|
90
|
+
const post = await evaluatePolicies("PostToolUse", postPayload, session);
|
|
91
|
+
collectHits(post, "PostToolUse", out);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function collectHits(
|
|
98
|
+
result: EvaluationResult,
|
|
99
|
+
eventType: "PreToolUse" | "PostToolUse",
|
|
100
|
+
out: ReplayHit[],
|
|
101
|
+
): void {
|
|
102
|
+
// `policyNames` is set when multiple policies fired (sanitize stack);
|
|
103
|
+
// otherwise fall back to `policyName`.
|
|
104
|
+
const names = result.policyNames && result.policyNames.length > 0
|
|
105
|
+
? result.policyNames
|
|
106
|
+
: result.policyName
|
|
107
|
+
? [result.policyName]
|
|
108
|
+
: [];
|
|
109
|
+
for (const name of names) {
|
|
110
|
+
// The aggregate `decision` reflects the most severe firing, but the audit
|
|
111
|
+
// wants per-policy counts. We re-tag each name with the aggregate decision
|
|
112
|
+
// for now — accurate enough for the table; a future audit-detail mode can
|
|
113
|
+
// re-evaluate per-policy if precision becomes important.
|
|
114
|
+
out.push({
|
|
115
|
+
policyName: name,
|
|
116
|
+
decision: result.decision,
|
|
117
|
+
reason: result.reason,
|
|
118
|
+
eventType,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|