failproofai 0.0.14 → 0.0.15-beta.1
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 +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +2 -2
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- 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 +2 -2
- 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 +3 -3
- 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 +2 -2
- 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/audit/invite/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- 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/audit/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- 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 +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- 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 +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +20 -8
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- 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 +3 -3
- 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 +3 -3
- 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 +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +3 -3
- 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]__0-219ec._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0hv08mx._.js → [root-of-the-server]__0-dh5jy._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0obaznk._.js → [root-of-the-server]__0h3lfmp._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0xvsnvt._.js → [root-of-the-server]__0l4vuqm._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sb_5m8._.js +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xuaoik._.js +6 -4
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1hkdkmz._.js → [root-of-the-server]__10oa-v9._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1i9rknc._.js → [root-of-the-server]__18d-9oi._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1is3glr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +6 -6
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0hdsupo._.js → [root-of-the-server]__0114ewg._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01od0m6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lpvwqm._.js +21 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ynf7tx._.js → [root-of-the-server]__0qucxyj._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1s2b6h0._.js → [root-of-the-server]__0x3anhr._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0y4wf1u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +10 -10
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00cz9as._.js → [root-of-the-server]__1apb7k_._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1py795m._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_077y5np._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1-3unkg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_0xkhw_v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_hermes-projects_ts_05b8-it._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0gkzjaa.js → node_modules_html-to-image_es_index_1wnt2ya.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +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 +22 -10
- package/.next/standalone/.next/static/chunks/{29o4j32z643_a.js → 05h7pau8d3rpj.js} +1 -1
- package/.next/standalone/.next/static/chunks/0oos4ntyvkmjl.js +1 -0
- package/.next/standalone/.next/static/chunks/0tuwf_7uhfn2y.js +6 -0
- package/.next/standalone/.next/static/chunks/1hdhteui932bq.js +1 -0
- package/.next/standalone/.next/static/chunks/1l6q2-8m0gk19.js +1 -0
- package/.next/standalone/.next/static/chunks/2geqsiz0n8e3w.js +1 -0
- package/.next/standalone/.next/static/chunks/{3-ogr79xqbw0h.js → 2mazwb1qmur1p.js} +1 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +2 -0
- package/.next/standalone/.next/static/chunks/{1ua5armwfph8o.js → 31fmsp1soxi07.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3s8aaig91937v.js → 37-so-zy208k7.js} +1 -1
- package/.next/standalone/.next/static/chunks/3a_o5v_sl858j.js +1 -0
- package/.next/standalone/.next/static/chunks/{02jcotghcxrbu.js → 3kmwfieo15mr2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2uh_4k4m296z5.js → 3p66lo2y-ezfr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{06z_ex1ts3n4s.js → 412ajx6jkb5_i.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-10hj8t5pf33tu.js → turbopack-1lsv92x7klwrn.js} +1 -1
- package/.next/standalone/app/actions/get-hooks-config.ts +125 -8
- package/.next/standalone/app/actions/update-hooks-config.ts +38 -1
- package/.next/standalone/app/components/project-list.tsx +229 -42
- package/.next/standalone/app/policies/hooks-client.tsx +149 -27
- package/.next/standalone/app/project/[name]/page.tsx +12 -2
- package/.next/standalone/integration-suite/README.md +43 -1
- package/.next/standalone/integration-suite/ci-entrypoint.sh +21 -6
- package/.next/standalone/integration-suite/install-clis.sh +58 -13
- package/.next/standalone/integration-suite/probe-cli.sh +51 -11
- package/.next/standalone/integration-suite/report.js +52 -6
- package/.next/standalone/integration-suite/run.sh +55 -3
- package/.next/standalone/lib/hermes-profiles.ts +156 -0
- package/.next/standalone/lib/hermes-projects.ts +91 -25
- package/.next/standalone/lib/hermes-sessions.ts +65 -10
- package/.next/standalone/lib/openclaw-projects.ts +111 -18
- package/.next/standalone/lib/openclaw-sessions.ts +20 -0
- package/.next/standalone/lib/paths.ts +36 -0
- package/.next/standalone/lib/project-tree.ts +193 -0
- package/.next/standalone/lib/projects.ts +22 -0
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/next-config-ts/transpile-config.js +3 -114
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/cli/next-test.js +2 -1
- package/.next/standalone/node_modules/next/dist/compiled/http-proxy/index.js +5 -5
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +11 -10
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +11 -10
- 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 +9 -9
- package/.next/standalone/node_modules/next/dist/lib/install-dependencies.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/lib/typescript/loadTsConfig.js +181 -0
- package/.next/standalone/node_modules/next/dist/lib/typescript/missingDependencyError.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeCheckCli.js +53 -0
- package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeScriptCli.js +256 -0
- package/.next/standalone/node_modules/next/dist/lib/verify-typescript-setup.js +97 -56
- package/.next/standalone/node_modules/next/dist/server/app-render/action-handler.js +129 -34
- package/.next/standalone/node_modules/next/dist/server/app-render/manifests-singleton.js +52 -4
- package/.next/standalone/node_modules/next/dist/server/config-schema.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/config-shared.js +1 -2
- 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/image-optimizer.js +8 -27
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +90 -53
- package/.next/standalone/node_modules/next/dist/server/lib/patch-fetch.js +15 -2
- package/.next/standalone/node_modules/next/dist/server/lib/router-utils/proxy-request.js +4 -0
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/web/spec-extension/unstable-cache.js +1 -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/shared/lib/router/utils/prepare-destination.js +2 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/server-reference-info.js +12 -0
- 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 +10 -10
- package/.next/standalone/package.json +7 -6
- package/.next/standalone/pi-extension/index.ts +38 -5
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +14 -13
- package/dist/cli.mjs +789 -399
- package/lib/hermes-profiles.ts +156 -0
- package/lib/hermes-projects.ts +91 -25
- package/lib/hermes-sessions.ts +65 -10
- package/lib/openclaw-projects.ts +111 -18
- package/lib/openclaw-sessions.ts +20 -0
- package/lib/paths.ts +36 -0
- package/lib/project-tree.ts +193 -0
- package/lib/projects.ts +22 -0
- package/package.json +7 -6
- package/pi-extension/index.ts +38 -5
- package/scripts/launch.ts +7 -0
- package/src/audit/cli-adapters/hermes.ts +6 -3
- package/src/audit/cli-adapters/openclaw.ts +17 -13
- package/src/hooks/configure-wizard.ts +24 -2
- package/src/hooks/custom-hooks-loader.ts +90 -13
- package/src/hooks/enforcement-capability.ts +266 -0
- package/src/hooks/handler.ts +20 -3
- package/src/hooks/hook-activity-store.ts +14 -0
- package/src/hooks/hooks-config.ts +90 -6
- package/src/hooks/integrations.ts +123 -19
- package/src/hooks/manager.ts +206 -75
- package/src/hooks/policy-evaluator.ts +59 -7
- package/src/hooks/policy-types.ts +31 -0
- package/src/hooks/types.ts +50 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0zf8xlq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fax1sl._.js +0 -19
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tkjqka._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +0 -3
- package/.next/standalone/.next/static/chunks/0o0ut0ugd0_-s.js +0 -1
- package/.next/standalone/.next/static/chunks/0xl97x-874k-u.js +0 -1
- package/.next/standalone/.next/static/chunks/19d7hqx8udopz.js +0 -1
- package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +0 -2
- package/.next/standalone/.next/static/chunks/370sj2lgkw5ey.js +0 -1
- package/.next/standalone/.next/static/chunks/3w7g0n0iwwnbs.js +0 -1
- package/.next/standalone/.next/static/chunks/42oeva1-w0z2d.js +0 -6
- /package/.next/standalone/.next/static/{WQRKgSfFekLcC_pvyzp4S → nhn6Tt0--xZKdgVGIqR6E}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{WQRKgSfFekLcC_pvyzp4S → nhn6Tt0--xZKdgVGIqR6E}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{WQRKgSfFekLcC_pvyzp4S → nhn6Tt0--xZKdgVGIqR6E}/_ssgManifest.js +0 -0
package/src/hooks/manager.ts
CHANGED
|
@@ -14,15 +14,16 @@ import {
|
|
|
14
14
|
type HookScope,
|
|
15
15
|
type IntegrationType,
|
|
16
16
|
} from "./types";
|
|
17
|
-
import { claudeCode, getIntegration } from "./integrations";
|
|
17
|
+
import { claudeCode, getIntegration, settingsPathsFor } from "./integrations";
|
|
18
18
|
import { promptPolicySelection } from "./install-prompt";
|
|
19
|
-
import { readMergedHooksConfig, readScopedHooksConfig, writeScopedHooksConfig } from "./hooks-config";
|
|
20
|
-
import type { HooksConfig } from "./policy-types";
|
|
19
|
+
import { configuredCustomPolicyPaths, readMergedHooksConfig, readScopedHooksConfig, writeScopedHooksConfig, syncConventionPolicies, findProjectConfigDir } from "./hooks-config";
|
|
20
|
+
import type { HooksConfig, ConventionPolicyRecord } from "./policy-types";
|
|
21
21
|
import { BUILTIN_POLICIES } from "./builtin-policies";
|
|
22
22
|
import { loadCustomHooks, discoverPolicyFiles } from "./custom-hooks-loader";
|
|
23
23
|
import { trackHookEvent } from "./hook-telemetry";
|
|
24
24
|
import { getInstanceId, hashToId } from "../../lib/telemetry-id";
|
|
25
25
|
import { CliError } from "../cli-error";
|
|
26
|
+
import { hookLogWarn } from "./hook-logger";
|
|
26
27
|
|
|
27
28
|
const VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
|
|
28
29
|
|
|
@@ -109,7 +110,7 @@ export async function installHooks(
|
|
|
109
110
|
cwd?: string,
|
|
110
111
|
includeBeta = false,
|
|
111
112
|
source?: string,
|
|
112
|
-
customPoliciesPath?: string,
|
|
113
|
+
customPoliciesPath?: string | string[],
|
|
113
114
|
removeCustomHooks = false,
|
|
114
115
|
cli?: IntegrationType[],
|
|
115
116
|
options: InstallHooksOptions = {},
|
|
@@ -140,7 +141,7 @@ async function installHooksImpl(
|
|
|
140
141
|
cwd?: string,
|
|
141
142
|
includeBeta = false,
|
|
142
143
|
source?: string,
|
|
143
|
-
customPoliciesPath?: string,
|
|
144
|
+
customPoliciesPath?: string | string[],
|
|
144
145
|
removeCustomHooks = false,
|
|
145
146
|
cli?: IntegrationType[],
|
|
146
147
|
replace = false,
|
|
@@ -211,62 +212,104 @@ async function installHooksImpl(
|
|
|
211
212
|
selectedPolicies = await promptPolicySelection(preSelected, { includeBeta });
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
// Preserve existing config fields
|
|
215
|
+
// Preserve existing config fields when updating. New writes use the plural
|
|
216
|
+
// form, while reads continue to accept the legacy singular field.
|
|
215
217
|
const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
|
|
216
218
|
if (removeCustomHooks) {
|
|
217
219
|
delete configToWrite.customPoliciesPath;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
220
|
+
delete configToWrite.customPoliciesPaths;
|
|
221
|
+
} else if (
|
|
222
|
+
customPoliciesPath &&
|
|
223
|
+
(typeof customPoliciesPath === "string" || customPoliciesPath.length > 0)
|
|
224
|
+
) {
|
|
225
|
+
const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath)
|
|
226
|
+
.map((path) => resolve(path));
|
|
227
|
+
|
|
228
|
+
// Additive by default, mirroring `enabledPolicies` directly above: a second
|
|
229
|
+
// `--custom` ADDS to what is configured rather than silently discarding it.
|
|
230
|
+
// Replacing was the surprising half of the old single-path field — running
|
|
231
|
+
// `-c a` then `-c b` left only `b`, with nothing printed to say `a` had
|
|
232
|
+
// stopped applying. `replace` (passed by the configure wizard) still makes
|
|
233
|
+
// the given set authoritative, exactly as it does for enabled policies.
|
|
234
|
+
//
|
|
235
|
+
// Carried-over paths are filtered by existence first: a file deleted after
|
|
236
|
+
// it was configured must not make every future install fail, which is what
|
|
237
|
+
// the strict validation below would do.
|
|
238
|
+
const carried = (
|
|
239
|
+
previousConfig.customPoliciesPaths ??
|
|
240
|
+
(previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])
|
|
241
|
+
)
|
|
242
|
+
.map((path) => resolve(path))
|
|
243
|
+
.filter((path) => {
|
|
244
|
+
if (existsSync(path)) return true;
|
|
245
|
+
console.log(`Dropping custom policies path (file no longer exists): ${path}`);
|
|
246
|
+
return false;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
configToWrite.customPoliciesPaths = replace
|
|
250
|
+
? [...new Set(incoming)]
|
|
251
|
+
: [...new Set([...carried, ...incoming])];
|
|
252
|
+
delete configToWrite.customPoliciesPath;
|
|
253
|
+
|
|
254
|
+
// Validate only what this invocation added. Carried-over paths were
|
|
255
|
+
// validated when they were added, and re-validating them here would let one
|
|
256
|
+
// stale file block an unrelated install.
|
|
257
|
+
for (const path of incoming) {
|
|
258
|
+
let validatedHooks: Awaited<ReturnType<typeof loadCustomHooks>> = [];
|
|
236
259
|
try {
|
|
237
|
-
await
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
260
|
+
validatedHooks = await loadCustomHooks(path, { strict: true });
|
|
261
|
+
} catch (err) {
|
|
262
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
263
|
+
try {
|
|
264
|
+
await trackHookEvent(getInstanceId(), "custom_policy_validation_failed", {
|
|
265
|
+
scope,
|
|
266
|
+
error_type: /not found/i.test(msg) ? "file_not_found" : "load_error",
|
|
267
|
+
});
|
|
268
|
+
} catch {}
|
|
269
|
+
console.error(`Error: ${msg}`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
if (validatedHooks.length === 0) {
|
|
273
|
+
try {
|
|
274
|
+
await trackHookEvent(getInstanceId(), "custom_policy_validation_failed", {
|
|
275
|
+
scope,
|
|
276
|
+
error_type: "no_hooks_registered",
|
|
277
|
+
});
|
|
278
|
+
} catch {}
|
|
279
|
+
console.error(
|
|
280
|
+
`Error: no hooks registered in ${path}. ` +
|
|
281
|
+
`Make sure your file calls customPolicies.add(...) at least once.`,
|
|
282
|
+
);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
console.log(
|
|
286
|
+
`\nValidated ${validatedHooks.length} custom hook(s) from ${path}: ${validatedHooks.map((h) => h.name).join(", ")}`,
|
|
245
287
|
);
|
|
246
|
-
process.exit(1);
|
|
247
288
|
}
|
|
248
|
-
console.log(
|
|
249
|
-
`\nValidated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`,
|
|
250
|
-
);
|
|
251
289
|
}
|
|
252
290
|
writeScopedHooksConfig(configToWrite, scope, cwd);
|
|
253
291
|
console.log(`\nEnabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}\n`);
|
|
254
292
|
if (removeCustomHooks) {
|
|
255
293
|
console.log("Custom hooks path cleared.");
|
|
256
|
-
} else if (configToWrite.customPoliciesPath) {
|
|
257
|
-
|
|
294
|
+
} else if (configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath) {
|
|
295
|
+
const paths = configToWrite.customPoliciesPaths ?? [configToWrite.customPoliciesPath!];
|
|
296
|
+
console.log(`Custom hooks paths: ${paths.join(", ")}`);
|
|
258
297
|
}
|
|
259
298
|
|
|
260
299
|
// Write hooks for each selected CLI
|
|
261
300
|
const writtenSettingsPaths: { cli: IntegrationType; path: string }[] = [];
|
|
262
301
|
for (const cliId of selectedClis) {
|
|
263
302
|
const integration = getIntegration(cliId);
|
|
264
|
-
|
|
303
|
+
// Usually one path; Hermes returns one per profile (each is a separate home
|
|
304
|
+
// dir with its own config.yaml, and a missed one runs unhooked in silence).
|
|
305
|
+
const settingsPaths = settingsPathsFor(integration, scope, cwd);
|
|
265
306
|
try {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
307
|
+
for (const settingsPath of settingsPaths) {
|
|
308
|
+
const settings = integration.readSettings(settingsPath);
|
|
309
|
+
integration.writeHookEntries(settings, binaryPath, scope);
|
|
310
|
+
integration.writeSettings(settingsPath, settings);
|
|
311
|
+
writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
|
|
312
|
+
}
|
|
270
313
|
} catch (err) {
|
|
271
314
|
const errorType = err instanceof Error && /EACCES|EPERM/.test(err.message)
|
|
272
315
|
? "permission_denied"
|
|
@@ -303,7 +346,7 @@ async function installHooksImpl(
|
|
|
303
346
|
arch: arch(),
|
|
304
347
|
os_release: release(),
|
|
305
348
|
hostname_hash: hashToId(hostname()),
|
|
306
|
-
has_custom_hooks_path: !!(configToWrite.customPoliciesPath),
|
|
349
|
+
has_custom_hooks_path: !!(configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath),
|
|
307
350
|
has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
|
|
308
351
|
param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
|
|
309
352
|
command_format: scope === "project" ? "npx" : "absolute",
|
|
@@ -382,6 +425,7 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
382
425
|
if (opts?.removeCustomHooks) {
|
|
383
426
|
const config = readScopedHooksConfig(configScope, cwd);
|
|
384
427
|
delete config.customPoliciesPath;
|
|
428
|
+
delete config.customPoliciesPaths;
|
|
385
429
|
writeScopedHooksConfig(config, configScope, cwd);
|
|
386
430
|
console.log("Custom hooks path cleared.");
|
|
387
431
|
}
|
|
@@ -452,9 +496,11 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
452
496
|
: [];
|
|
453
497
|
|
|
454
498
|
for (const s of scopesToRemove) {
|
|
455
|
-
|
|
499
|
+
// Usually one path; Hermes returns one per profile.
|
|
500
|
+
const settingsPaths = settingsPathsFor(integration, s, cwd);
|
|
501
|
+
const existing = settingsPaths.filter((p) => existsSync(p));
|
|
456
502
|
|
|
457
|
-
if (
|
|
503
|
+
if (existing.length === 0) {
|
|
458
504
|
if (scope !== "all" && selectedClis.length === 1) {
|
|
459
505
|
console.log("No settings file found. Nothing to remove.");
|
|
460
506
|
nothingToReport = true;
|
|
@@ -462,18 +508,22 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
462
508
|
continue;
|
|
463
509
|
}
|
|
464
510
|
|
|
465
|
-
|
|
466
|
-
|
|
511
|
+
let removedHere = 0;
|
|
512
|
+
for (const settingsPath of existing) {
|
|
513
|
+
const removed = integration.removeHooksFromFile(settingsPath);
|
|
514
|
+
removedHere += removed;
|
|
515
|
+
if (removed > 0 && scope !== "all") {
|
|
516
|
+
console.log(`Removed ${removed} failproofai hook(s) from ${integration.displayName} settings.`);
|
|
517
|
+
console.log(`Settings: ${settingsPath}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (removedHere === 0 && scope !== "all" && selectedClis.length === 1) {
|
|
467
522
|
console.log("No hooks found in settings. Nothing to remove.");
|
|
468
523
|
nothingToReport = true;
|
|
469
524
|
continue;
|
|
470
525
|
}
|
|
471
|
-
totalRemoved +=
|
|
472
|
-
|
|
473
|
-
if (scope !== "all") {
|
|
474
|
-
console.log(`Removed ${removed} failproofai hook(s) from ${integration.displayName} settings.`);
|
|
475
|
-
console.log(`Settings: ${settingsPath}`);
|
|
476
|
-
}
|
|
526
|
+
totalRemoved += removedHere;
|
|
477
527
|
}
|
|
478
528
|
}
|
|
479
529
|
|
|
@@ -484,7 +534,9 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
484
534
|
for (const cliId of selectedClis) {
|
|
485
535
|
const integration = getIntegration(cliId);
|
|
486
536
|
for (const s of integration.scopes) {
|
|
487
|
-
|
|
537
|
+
for (const p of settingsPathsFor(integration, s, cwd)) {
|
|
538
|
+
console.log(` ${integration.displayName} / ${s}: ${p}`);
|
|
539
|
+
}
|
|
488
540
|
}
|
|
489
541
|
}
|
|
490
542
|
}
|
|
@@ -513,14 +565,14 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
513
565
|
// Clear config across all three scopes
|
|
514
566
|
for (const s of HOOK_SCOPES) {
|
|
515
567
|
const existing = readScopedHooksConfig(s, cwd);
|
|
516
|
-
if (existing.enabledPolicies.length > 0 || existing.customPoliciesPath || existing.policyParams) {
|
|
517
|
-
const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
|
|
568
|
+
if (existing.enabledPolicies.length > 0 || existing.customPoliciesPaths?.length || existing.customPoliciesPath || existing.policyParams) {
|
|
569
|
+
const { customPoliciesPath: _drop, customPoliciesPaths: _dropMany, policyParams: _dropParams, ...rest } = existing;
|
|
518
570
|
writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
|
|
519
571
|
}
|
|
520
572
|
}
|
|
521
573
|
} else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
|
|
522
574
|
const existing = readScopedHooksConfig(configScope, cwd);
|
|
523
|
-
const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
|
|
575
|
+
const { customPoliciesPath: _drop, customPoliciesPaths: _dropMany, policyParams: _dropParams, ...rest } = existing;
|
|
524
576
|
writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
|
|
525
577
|
}
|
|
526
578
|
}
|
|
@@ -540,6 +592,7 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
540
592
|
export async function listHooks(cwd?: string): Promise<void> {
|
|
541
593
|
const config = readMergedHooksConfig(cwd);
|
|
542
594
|
const enabledSet = new Set(config.enabledPolicies);
|
|
595
|
+
const disabledCustomSet = new Set(config.disabledCustomPolicies ?? []);
|
|
543
596
|
|
|
544
597
|
// Determine which scopes have hooks installed (deduplicate when paths overlap, e.g. cwd === home)
|
|
545
598
|
const uniqueScopes = deduplicateScopes(HOOK_SCOPES, cwd);
|
|
@@ -676,19 +729,29 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
676
729
|
}
|
|
677
730
|
}
|
|
678
731
|
|
|
679
|
-
// Custom Policies section
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
732
|
+
// Explicit Custom Policies section
|
|
733
|
+
const explicitPaths = configuredCustomPolicyPaths(config);
|
|
734
|
+
if (explicitPaths.length > 0) {
|
|
735
|
+
console.log(`\n \u2500\u2500 Custom Policies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
736
|
+
for (const path of explicitPaths) {
|
|
737
|
+
// Enforcement resolves configured paths from the project config root.
|
|
738
|
+
// Use the same canonical path here so the ID checked by the CLI exactly
|
|
739
|
+
// matches the ID written by the dashboard.
|
|
740
|
+
const absPath = resolve(findProjectConfigDir(cwd ?? process.cwd()), path);
|
|
741
|
+
console.log(` ${absPath}`);
|
|
742
|
+
if (!existsSync(absPath)) {
|
|
743
|
+
console.log(` \x1B[31m\u2717 File not found: ${absPath}\x1B[0m`);
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
const hooks = await loadCustomHooks(absPath);
|
|
686
747
|
if (hooks.length === 0) {
|
|
687
748
|
console.log(` \x1B[31m\u2717 ERR failed to load (check ~/.failproofai/logs/hooks.log)\x1B[0m`);
|
|
688
749
|
} else {
|
|
689
750
|
const descColWidth = nameColWidth;
|
|
690
751
|
for (const hook of hooks) {
|
|
691
|
-
|
|
752
|
+
const disabled = disabledCustomSet.has(`custom:${absPath}:${hook.name}`);
|
|
753
|
+
const status = disabled ? "\x1B[2m OFF\x1B[0m" : "\x1B[32m\u2713 ON\x1B[0m";
|
|
754
|
+
console.log(` ${status} ${hook.name.padEnd(descColWidth)}${hook.description ?? ""}`);
|
|
692
755
|
}
|
|
693
756
|
}
|
|
694
757
|
}
|
|
@@ -696,33 +759,101 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
696
759
|
}
|
|
697
760
|
|
|
698
761
|
// Convention Policies section (.failproofai/policies/*policies.{js,mjs,ts})
|
|
699
|
-
|
|
762
|
+
// Walk up to the project root like enforcement does
|
|
763
|
+
// (custom-hooks-loader -> findProjectConfigDir); resolving at the exact cwd
|
|
764
|
+
// meant running this from a subdirectory listed no project policies while the
|
|
765
|
+
// hook path was loading them.
|
|
766
|
+
const base = findProjectConfigDir(cwd ?? process.cwd());
|
|
767
|
+
const projectDir = resolve(base, ".failproofai", "policies");
|
|
768
|
+
const userDir = resolve(homedir(), ".failproofai", "policies");
|
|
769
|
+
const sameDir = userDir === projectDir;
|
|
700
770
|
const conventionDirs: { label: string; dir: string }[] = [
|
|
701
|
-
{ label: "Project"
|
|
702
|
-
|
|
771
|
+
{ label: sameDir ? "Project + User" : "Project", dir: projectDir },
|
|
772
|
+
// Running from $HOME makes both paths identical. Listing the directory
|
|
773
|
+
// twice printed every file a second time as "failed to load" — the file was
|
|
774
|
+
// already imported by the first pass, so the module cache short-circuits
|
|
775
|
+
// `customPolicies.add` and `loadCustomHooks` legitimately returns 0 hooks.
|
|
776
|
+
// Nothing was wrong with the policy; the second listing was.
|
|
777
|
+
...(sameDir ? [] : [{ label: "User", dir: userDir }]),
|
|
703
778
|
];
|
|
704
779
|
|
|
780
|
+
// Record of what was found, mirrored into policies-config.json below so the
|
|
781
|
+
// config shows installed convention policies and not only enabled builtins.
|
|
782
|
+
const discovered: Record<"project" | "user", ConventionPolicyRecord[]> = {
|
|
783
|
+
project: [],
|
|
784
|
+
user: [],
|
|
785
|
+
};
|
|
786
|
+
|
|
705
787
|
for (const { label, dir } of conventionDirs) {
|
|
706
788
|
const files = discoverPolicyFiles(dir);
|
|
707
789
|
if (files.length === 0) continue;
|
|
708
790
|
|
|
791
|
+
// A shared project/user directory is listed once but belongs to both, so
|
|
792
|
+
// its record is written to both scopes' config files.
|
|
793
|
+
const targets: ("project" | "user")[] =
|
|
794
|
+
dir === projectDir && dir === userDir ? ["project", "user"] : dir === projectDir ? ["project"] : ["user"];
|
|
795
|
+
// When both directories are the same, runtime discovery loads the file as
|
|
796
|
+
// project convention policy and skips the duplicate user pass.
|
|
797
|
+
const policyScope: "project" | "user" = dir === projectDir ? "project" : "user";
|
|
798
|
+
const record = (file: string, hooks: string[]) => {
|
|
799
|
+
for (const t of targets) discovered[t].push({ file, hooks });
|
|
800
|
+
};
|
|
801
|
+
|
|
709
802
|
console.log(`\n \u2500\u2500 Convention Policies \u2014 ${label} (${dir}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
803
|
+
// `nameColWidth` is sized to the longest BUILTIN name, but a convention
|
|
804
|
+
// filename can be longer \u2014 `padEnd` is then a no-op and the hook count runs
|
|
805
|
+
// straight into the filename with no space
|
|
806
|
+
// (`enforce-bengaluru-event-links-policies.mjs1 hook(s)`). Widen to fit the
|
|
807
|
+
// files actually being printed, keeping a two-space gutter.
|
|
808
|
+
const colWidth = Math.max(nameColWidth, ...files.map((f) => basename(f).length + 2));
|
|
710
809
|
for (const file of files) {
|
|
711
810
|
try {
|
|
712
811
|
const hooks = await loadCustomHooks(file);
|
|
812
|
+
const filename = basename(file);
|
|
813
|
+
record(filename, hooks.map((h) => h.name));
|
|
713
814
|
if (hooks.length === 0) {
|
|
714
|
-
|
|
715
|
-
console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
|
|
815
|
+
console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(colWidth)}\x1B[31mfailed to load\x1B[0m`);
|
|
716
816
|
} else {
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
817
|
+
const hookStates = hooks.map((hook) => ({
|
|
818
|
+
hook,
|
|
819
|
+
disabled: disabledCustomSet.has(`convention:${policyScope}:${filename}:${hook.name}`),
|
|
820
|
+
}));
|
|
821
|
+
const disabledCount = hookStates.filter((entry) => entry.disabled).length;
|
|
822
|
+
const status = disabledCount === 0
|
|
823
|
+
? "\x1B[32m\u2713 ON\x1B[0m"
|
|
824
|
+
: disabledCount === hooks.length
|
|
825
|
+
? "\x1B[2m OFF\x1B[0m"
|
|
826
|
+
: "\x1B[33m\u25D0 MIXED\x1B[0m";
|
|
827
|
+
const hookSummary = hookStates
|
|
828
|
+
.map(({ hook, disabled }) => `${hook.name}${disabled ? " (OFF)" : ""}`)
|
|
829
|
+
.join(", ");
|
|
830
|
+
console.log(` ${status} ${filename.padEnd(colWidth)}${hooks.length} hook(s): ${hookSummary}`);
|
|
720
831
|
}
|
|
721
832
|
} catch {
|
|
722
833
|
const filename = basename(file);
|
|
723
|
-
|
|
834
|
+
record(filename, []);
|
|
835
|
+
console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(colWidth)}\x1B[31merror\x1B[0m`);
|
|
724
836
|
}
|
|
725
837
|
}
|
|
726
838
|
console.log();
|
|
727
839
|
}
|
|
840
|
+
|
|
841
|
+
// Mirror what was just listed into the USER config. Safe here because
|
|
842
|
+
// `failproofai policies` is a one-shot command — never do this on the hook
|
|
843
|
+
// path (see the HooksConfig.conventionPolicies doc comment).
|
|
844
|
+
//
|
|
845
|
+
// User scope only, deliberately. A project's `.failproofai/policies-config.json`
|
|
846
|
+
// is routinely committed (this repo tracks its own), so writing the record
|
|
847
|
+
// there would make a plain `failproofai policies` dirty the working tree and
|
|
848
|
+
// put a spurious diff in front of every contributor — a read command must not
|
|
849
|
+
// do that. `discovered.project` is still collected so the listing above can
|
|
850
|
+
// report it; it simply is not persisted.
|
|
851
|
+
try {
|
|
852
|
+
syncConventionPolicies(discovered.user, "user");
|
|
853
|
+
} catch (err) {
|
|
854
|
+
// Listing must never fail because the mirror could not be written.
|
|
855
|
+
hookLogWarn(
|
|
856
|
+
`could not record convention policies in config: ${err instanceof Error ? err.message : String(err)}`,
|
|
857
|
+
);
|
|
858
|
+
}
|
|
728
859
|
}
|
|
@@ -179,6 +179,21 @@ export async function evaluatePolicies(
|
|
|
179
179
|
decision: "deny",
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
|
+
// `beforeSubmitPrompt` does NOT read `permission`. Its only block key
|
|
183
|
+
// is `continue: false` (+ an optional `user_message` shown to the
|
|
184
|
+
// user); an object carrying unknown keys validates and is dropped, so
|
|
185
|
+
// the flat deny below was inert and every prompt went through. Verified
|
|
186
|
+
// against cursor-agent 2026.07.16-899851b, 1931.index.js char 887883.
|
|
187
|
+
if (eventType === "UserPromptSubmit") {
|
|
188
|
+
return {
|
|
189
|
+
exitCode: 0,
|
|
190
|
+
stdout: JSON.stringify({ continue: false, user_message: blockedMessage }),
|
|
191
|
+
stderr: "",
|
|
192
|
+
policyName: policy.name,
|
|
193
|
+
reason,
|
|
194
|
+
decision: "deny",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
182
197
|
const response = {
|
|
183
198
|
permission: "deny",
|
|
184
199
|
user_message: blockedMessage,
|
|
@@ -229,13 +244,15 @@ export async function evaluatePolicies(
|
|
|
229
244
|
}
|
|
230
245
|
|
|
231
246
|
// Hermes: the block contract is `{"decision":"block","reason"}` on stdout;
|
|
232
|
-
// Hermes IGNORES exit codes, so the JSON is the only channel.
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
247
|
+
// Hermes IGNORES exit codes, so the JSON is the only channel. A block on
|
|
248
|
+
// PreToolUse stops the tool before it runs, regardless of the originating
|
|
249
|
+
// platform (slack/telegram/cli/cron) or subagent.
|
|
250
|
+
//
|
|
251
|
+
// Only `pre_tool_call` and `pre_verify` are gated in upstream's
|
|
252
|
+
// `_parse_response` (agent/shell_hooks.py:567-621); the shape is still
|
|
253
|
+
// emitted for the other installed events, where it is read by nothing.
|
|
254
|
+
// We do not install `pre_verify` (see the note in types.ts), so Hermes
|
|
255
|
+
// has no canonical Stop event and no Stop branch is needed here.
|
|
239
256
|
if (session?.cli === "hermes") {
|
|
240
257
|
return {
|
|
241
258
|
exitCode: 0,
|
|
@@ -422,6 +439,41 @@ export async function evaluatePolicies(
|
|
|
422
439
|
};
|
|
423
440
|
}
|
|
424
441
|
|
|
442
|
+
// Copilot reads two shapes we were not sending, so a deny on either of
|
|
443
|
+
// these events was emitted, logged, counted as enforcement — and ignored.
|
|
444
|
+
// Both verified against the shipped @github/copilot-linux-x64 bundle.
|
|
445
|
+
// Note exit 2 is NEVER a deny channel on copilot for any event; it is
|
|
446
|
+
// logged as `Hook command exited with code 2 (warning)`.
|
|
447
|
+
if (session?.cli === "copilot") {
|
|
448
|
+
// `userPromptSubmit` gates the turn, but only on {decision:"block"} at
|
|
449
|
+
// exit 0 (consumer app.js@2823018). We were emitting exit 2 + stderr,
|
|
450
|
+
// which copilot warns about and then submits the prompt anyway.
|
|
451
|
+
if (eventType === "UserPromptSubmit") {
|
|
452
|
+
return {
|
|
453
|
+
exitCode: 0,
|
|
454
|
+
stdout: JSON.stringify({ decision: "block", reason: blockedMessage }),
|
|
455
|
+
stderr: "",
|
|
456
|
+
policyName: policy.name,
|
|
457
|
+
reason,
|
|
458
|
+
decision: "deny",
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
// Copilot consumes a FLAT {behavior, message} here (normalizer
|
|
462
|
+
// CMn@179042 -> mapper h4t@2686538). The Codex-shaped nested
|
|
463
|
+
// hookSpecificOutput.decision below normalizes to `{}` on copilot, so
|
|
464
|
+
// the permission prompt proceeded as if no policy existed.
|
|
465
|
+
if (eventType === "PermissionRequest") {
|
|
466
|
+
return {
|
|
467
|
+
exitCode: 0,
|
|
468
|
+
stdout: JSON.stringify({ behavior: "deny", message: blockedMessage }),
|
|
469
|
+
stderr: "",
|
|
470
|
+
policyName: policy.name,
|
|
471
|
+
reason,
|
|
472
|
+
decision: "deny",
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
425
477
|
if (eventType === "PermissionRequest") {
|
|
426
478
|
// Codex-only: hookSpecificOutput.decision.behavior = "allow" | "deny"
|
|
427
479
|
// (per https://developers.openai.com/codex/hooks#permissionrequest).
|
|
@@ -84,7 +84,12 @@ export interface HooksConfig {
|
|
|
84
84
|
enabledPolicies: string[];
|
|
85
85
|
llm?: LlmConfig;
|
|
86
86
|
policyParams?: Record<string, Record<string, unknown>>;
|
|
87
|
+
/** Explicit custom policy files, loaded in array order. */
|
|
88
|
+
customPoliciesPaths?: string[];
|
|
89
|
+
/** @deprecated Use customPoliciesPaths. Kept for existing config files. */
|
|
87
90
|
customPoliciesPath?: string;
|
|
91
|
+
/** Source-qualified custom policy IDs that should not be registered. */
|
|
92
|
+
disabledCustomPolicies?: string[];
|
|
88
93
|
/**
|
|
89
94
|
* Turn off convention-discovered custom policies (`.failproofai/policies/`)
|
|
90
95
|
* without deleting or renaming the files. Absent means enabled — the default
|
|
@@ -93,4 +98,30 @@ export interface HooksConfig {
|
|
|
93
98
|
* Only ever written explicitly as `false`.
|
|
94
99
|
*/
|
|
95
100
|
customPoliciesEnabled?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Record of the convention policies (`.failproofai/policies/*policies.mjs`)
|
|
103
|
+
* discovered at this scope the last time a failproofai command ran.
|
|
104
|
+
*
|
|
105
|
+
* DESCRIPTIVE, NEVER AUTHORITATIVE. Enforcement always discovers from the
|
|
106
|
+
* filesystem (`loadAllCustomHooks`) and never reads this key — a policy file
|
|
107
|
+
* dropped in enforces on the very next tool call whether or not this list has
|
|
108
|
+
* caught up. Making the loader trust it would mean a freshly-copied policy
|
|
109
|
+
* silently doing nothing until some command refreshed the config, which is
|
|
110
|
+
* the exact silent-non-enforcement failure this project exists to remove.
|
|
111
|
+
*
|
|
112
|
+
* Refreshed wholesale (not merged) by `failproofai policies` and the setup
|
|
113
|
+
* wizard, so a deleted file disappears from the record on the next run. It is
|
|
114
|
+
* deliberately NOT written from the hook path: that runs on every tool call,
|
|
115
|
+
* and a read-modify-write from concurrent short-lived hook processes with no
|
|
116
|
+
* locking would corrupt the file that governs enforcement.
|
|
117
|
+
*/
|
|
118
|
+
conventionPolicies?: ConventionPolicyRecord[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** One convention policy file and the hooks it registered, as last observed. */
|
|
122
|
+
export interface ConventionPolicyRecord {
|
|
123
|
+
/** Basename, e.g. `hermes-guardrails.policies.mjs`. */
|
|
124
|
+
file: string;
|
|
125
|
+
/** Hook names the file registered, or `[]` if it failed to load. */
|
|
126
|
+
hooks: string[];
|
|
96
127
|
}
|
package/src/hooks/types.ts
CHANGED
|
@@ -105,8 +105,34 @@ export const HERMES_TOOL_INPUT_MAP: Record<string, Record<string, string>> = {
|
|
|
105
105
|
// (`~/.hermes/config.yaml`; Hermes has no project scope). `pre_tool_call` is the
|
|
106
106
|
// core deny point — it fires for tool calls from every source
|
|
107
107
|
// (slack/telegram/cli/cron) and internal subagents, so a single install
|
|
108
|
-
// intercepts all platforms.
|
|
109
|
-
//
|
|
108
|
+
// intercepts all platforms.
|
|
109
|
+
//
|
|
110
|
+
// `pre_verify` IS a turn-end gate — the earlier claim here that Hermes has none
|
|
111
|
+
// was wrong. We deliberately do NOT install it (product decision, 2026-07-29);
|
|
112
|
+
// this note records what it would buy so the choice can be revisited without
|
|
113
|
+
// re-deriving it.
|
|
114
|
+
//
|
|
115
|
+
// Upstream fires it once per turn when the agent has edited code and is about to
|
|
116
|
+
// finish (`agent/conversation_loop.py:6754`), and its parser accepts our Claude
|
|
117
|
+
// Stop shape verbatim — `{decision:"block", reason}` means "block the stop",
|
|
118
|
+
// i.e. keep going (`agent/shell_hooks.py:606-615`). The reason is injected as a
|
|
119
|
+
// synthetic user message and the loop re-enters
|
|
120
|
+
// (`conversation_loop.py:6774-6800`). Installing it is one entry in
|
|
121
|
+
// config.yaml — it is a shell hook like the other five, no Python involved.
|
|
122
|
+
//
|
|
123
|
+
// It would make the 5 `require-*-before-stop` builtins fire on Hermes, subject
|
|
124
|
+
// to three upstream conditions:
|
|
125
|
+
// 1. It fires ONLY on turns that landed a file mutation, and that means
|
|
126
|
+
// exactly `write_file` or `patch` (`agent/tool_result_classification.py:9`)
|
|
127
|
+
// — a turn that did its work through `terminal` (sed -i, rm, >) does not
|
|
128
|
+
// qualify, so a chat-only gateway may never see it.
|
|
129
|
+
// 2. Capped at 3 nudges per turn (`DEFAULT_MAX_VERIFY_NUDGES`,
|
|
130
|
+
// `agent/verify_hooks.py:21`), operator-overridable; resets each turn.
|
|
131
|
+
// 3. It landed upstream ~2026-06-30. Older Hermes fails the key against
|
|
132
|
+
// VALID_HOOKS and warn-and-skips it SILENTLY (`agent/shell_hooks.py:325`).
|
|
133
|
+
//
|
|
134
|
+
// Until it is installed, `HERMES_EVENT_MAP` emits no `Stop` and those 5
|
|
135
|
+
// builtins remain inapplicable on Hermes.
|
|
110
136
|
export const HERMES_HOOK_SCOPES = ["user"] as const;
|
|
111
137
|
export type HermesHookScope = (typeof HERMES_HOOK_SCOPES)[number];
|
|
112
138
|
|
|
@@ -1002,8 +1028,30 @@ export const HOOK_EVENT_TYPES = [
|
|
|
1002
1028
|
"Setup",
|
|
1003
1029
|
] as const;
|
|
1004
1030
|
|
|
1031
|
+
|
|
1032
|
+
|
|
1005
1033
|
export type HookEventType = (typeof HOOK_EVENT_TYPES)[number];
|
|
1006
1034
|
|
|
1035
|
+
/**
|
|
1036
|
+
* Events failproofai actually INSTALLS a hook for on Claude Code.
|
|
1037
|
+
*
|
|
1038
|
+
* Everything in `HOOK_EVENT_TYPES` except `WorktreeCreate`, which is not a
|
|
1039
|
+
* permission gate at all: Claude uses it as a worktree-PATH PROVIDER, taking
|
|
1040
|
+
* the stdout of the first hook that succeeds as the directory to create and
|
|
1041
|
+
* failing with "WorktreeCreate hook failed" when no hook supplies one. Our
|
|
1042
|
+
* allow path writes nothing to stdout — correctly, by the contract every other
|
|
1043
|
+
* event uses — so merely being registered there broke `claude --worktree` and
|
|
1044
|
+
* `/worktree` for every user, whatever any policy decided.
|
|
1045
|
+
*
|
|
1046
|
+
* It stays in `HOOK_EVENT_TYPES` because that list is the canonical set a
|
|
1047
|
+
* policy may subscribe to; this one governs what we write into settings.json.
|
|
1048
|
+
* No builtin matches it (all 39 match only PreToolUse / PostToolUse /
|
|
1049
|
+
* PermissionRequest / Stop), so nothing is lost by not registering.
|
|
1050
|
+
*/
|
|
1051
|
+
export const CLAUDE_INSTALL_EVENT_TYPES = HOOK_EVENT_TYPES.filter(
|
|
1052
|
+
(e) => e !== "WorktreeCreate",
|
|
1053
|
+
) as readonly HookEventType[];
|
|
1054
|
+
|
|
1007
1055
|
export const FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__" as const;
|
|
1008
1056
|
|
|
1009
1057
|
export interface ClaudeHookEntry {
|