failproofai 0.0.14-beta.2 → 0.0.15-beta.0
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 +8 -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]__0s89hoe._.js → [root-of-the-server]__02r5bgf._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__09v9cwb._.js → [root-of-the-server]__04o-kw7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qeuy9c._.js → [root-of-the-server]__0e8sjqm._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1tkjqka._.js → [root-of-the-server]__0lkzqax._.js} +3 -3
- 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]__03lv-pe._.js → [root-of-the-server]__0u02miy._.js} +3 -3
- 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]__170799-._.js +8 -8
- 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]__1i21qhv._.js +3 -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]__015_i4t._.js → [root-of-the-server]__1zqz4v8._.js} +3 -3
- 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_audit_loading_tsx_1j1kc6j._.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_06xsmte.js → node_modules_html-to-image_es_index_0w82veb.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 +10 -10
- package/.next/standalone/.next/static/chunks/0hn30_j22wpp_.js +6 -0
- package/.next/standalone/.next/static/chunks/{2llkdafnaejw_.js → 1_20f96f1253b.js} +1 -1
- package/.next/standalone/.next/static/chunks/1hdhteui932bq.js +1 -0
- package/.next/standalone/.next/static/chunks/{33u59vf_8xpd-.js → 1j2xm011_aoae.js} +1 -1
- package/.next/standalone/.next/static/chunks/{388hgfqye67xa.js → 1qzsbft-jtnf8.js} +1 -1
- 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/2xbkqlma_-61k.js +1 -0
- package/.next/standalone/.next/static/chunks/{29zkom4s9de2d.js → 2y7acsw0--8xf.js} +1 -1
- package/.next/standalone/.next/static/chunks/3060a-0fayz96.js +1 -0
- package/.next/standalone/.next/static/chunks/312ihvhxeuoi_.js +1 -0
- package/.next/standalone/.next/static/chunks/31c084gsop1bb.js +1 -0
- package/.next/standalone/.next/static/chunks/{1ua5armwfph8o.js → 31fmsp1soxi07.js} +1 -1
- package/.next/standalone/.next/static/chunks/{02jcotghcxrbu.js → 3kmwfieo15mr2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1gg-8e9q_m__9.js → 3q9y67-fobgpc.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 +85 -0
- package/.next/standalone/app/components/log-viewer/entry-row.tsx +8 -3
- package/.next/standalone/app/components/project-list.tsx +229 -42
- package/.next/standalone/app/components/raw-log-viewer.tsx +33 -15
- package/.next/standalone/app/policies/hooks-client.tsx +113 -15
- 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/entry-keys.ts +36 -0
- 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 +9 -7
- package/.next/standalone/pi-extension/index.ts +38 -5
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +623 -308
- package/lib/entry-keys.ts +36 -0
- 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 +9 -7
- 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 +58 -4
- package/src/hooks/enforcement-capability.ts +266 -0
- package/src/hooks/handler.ts +12 -1
- package/src/hooks/hook-activity-store.ts +14 -0
- package/src/hooks/hooks-config.ts +59 -1
- package/src/hooks/integrations.ts +123 -19
- package/src/hooks/manager.ts +93 -27
- package/src/hooks/policy-evaluator.ts +59 -7
- package/src/hooks/policy-types.ts +26 -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]__030k0c6._.js +0 -3
- 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]__0twi4fj._.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/_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/0h-6ymo9v86b0.js +0 -6
- package/.next/standalone/.next/static/chunks/0pndjhcxfu-0t.js +0 -1
- package/.next/standalone/.next/static/chunks/1-33f82_l32or.js +0 -1
- package/.next/standalone/.next/static/chunks/1bg_g3g359c_d.js +0 -1
- package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +0 -2
- package/.next/standalone/.next/static/chunks/396764jgd7-9j.js +0 -1
- package/.next/standalone/.next/static/chunks/3w7g0n0iwwnbs.js +0 -1
- /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_ssgManifest.js +0 -0
|
@@ -12,8 +12,10 @@ import { resolve, dirname } from "node:path";
|
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { parseDocument, type Document } from "yaml";
|
|
15
|
+
import { listHermesProfiles, hermesRoot } from "../../lib/hermes-profiles";
|
|
15
16
|
import {
|
|
16
17
|
HOOK_EVENT_TYPES,
|
|
18
|
+
CLAUDE_INSTALL_EVENT_TYPES,
|
|
17
19
|
HOOK_SCOPES,
|
|
18
20
|
CODEX_HOOK_EVENT_TYPES,
|
|
19
21
|
CODEX_HOOK_SCOPES,
|
|
@@ -118,6 +120,18 @@ export interface Integration {
|
|
|
118
120
|
/** Resolve the per-scope settings/hooks file path. */
|
|
119
121
|
getSettingsPath(scope: HookScope, cwd?: string): string;
|
|
120
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Every settings file this integration must write for a scope.
|
|
125
|
+
*
|
|
126
|
+
* Optional — defaults to `[getSettingsPath(scope, cwd)]`, which is right for
|
|
127
|
+
* every CLI whose scope maps to exactly one file. Hermes overrides it because
|
|
128
|
+
* each of its PROFILES is a separate home dir with its own `config.yaml`:
|
|
129
|
+
* writing only `~/.hermes/config.yaml` would leave every other profile running
|
|
130
|
+
* unhooked, silently. Use `settingsPathsFor(integration, …)` at call sites
|
|
131
|
+
* rather than calling this directly.
|
|
132
|
+
*/
|
|
133
|
+
getSettingsPaths?(scope: HookScope, cwd?: string): string[];
|
|
134
|
+
|
|
121
135
|
/** Read the raw settings/hooks file (returns {} when missing). */
|
|
122
136
|
readSettings(settingsPath: string): Record<string, unknown>;
|
|
123
137
|
|
|
@@ -143,13 +157,28 @@ export interface Integration {
|
|
|
143
157
|
detectInstalled(): boolean;
|
|
144
158
|
}
|
|
145
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Every settings file to write/remove for `(integration, scope)`.
|
|
162
|
+
*
|
|
163
|
+
* Single-file integrations (all but Hermes) return exactly one path, so callers
|
|
164
|
+
* can loop unconditionally.
|
|
165
|
+
*/
|
|
166
|
+
export function settingsPathsFor(
|
|
167
|
+
integration: Integration,
|
|
168
|
+
scope: HookScope,
|
|
169
|
+
cwd?: string,
|
|
170
|
+
): string[] {
|
|
171
|
+
const paths = integration.getSettingsPaths?.(scope, cwd);
|
|
172
|
+
return paths && paths.length > 0 ? paths : [integration.getSettingsPath(scope, cwd)];
|
|
173
|
+
}
|
|
174
|
+
|
|
146
175
|
// ── Claude Code integration ─────────────────────────────────────────────────
|
|
147
176
|
|
|
148
177
|
export const claudeCode: Integration = {
|
|
149
178
|
id: "claude",
|
|
150
179
|
displayName: "Claude Code",
|
|
151
180
|
scopes: HOOK_SCOPES,
|
|
152
|
-
eventTypes:
|
|
181
|
+
eventTypes: CLAUDE_INSTALL_EVENT_TYPES,
|
|
153
182
|
|
|
154
183
|
getSettingsPath(scope, cwd) {
|
|
155
184
|
const base = cwd ? resolve(cwd) : process.cwd();
|
|
@@ -196,7 +225,48 @@ export const claudeCode: Integration = {
|
|
|
196
225
|
const s = settings as ClaudeSettings;
|
|
197
226
|
if (!s.hooks) s.hooks = {};
|
|
198
227
|
|
|
199
|
-
|
|
228
|
+
// Drop our hook from any event we no longer install. Without this, an
|
|
229
|
+
// event removed from the list stays in settings.json forever on existing
|
|
230
|
+
// machines — reinstalling would not repair it — which is exactly the
|
|
231
|
+
// situation `WorktreeCreate` created: registered, silent on allow, and
|
|
232
|
+
// therefore breaking `claude --worktree` until hand-edited. Only OUR
|
|
233
|
+
// marked entries are touched; anyone else's hooks on the same event stay.
|
|
234
|
+
//
|
|
235
|
+
// This walks EVERY key the file happens to carry, including ones
|
|
236
|
+
// failproofai has never written — a newer Claude event, another tool's
|
|
237
|
+
// entry, a typo. Their values are therefore unvalidated input, and a
|
|
238
|
+
// non-array one (`"Foo": {}` or `"Foo": "bar"`) makes the iteration below
|
|
239
|
+
// throw. That aborts the whole install AFTER the policies were recorded as
|
|
240
|
+
// enabled, so the user is left believing they are covered while no hook was
|
|
241
|
+
// written at all — the silent non-enforcement this file exists to prevent.
|
|
242
|
+
// Skip anything not shaped like a matcher list and leave it untouched.
|
|
243
|
+
const installed = new Set<string>(CLAUDE_INSTALL_EVENT_TYPES);
|
|
244
|
+
for (const eventType of Object.keys(s.hooks)) {
|
|
245
|
+
if (installed.has(eventType)) continue;
|
|
246
|
+
const matchers = s.hooks[eventType];
|
|
247
|
+
if (!Array.isArray(matchers)) continue;
|
|
248
|
+
// Drop a matcher group only when WE emptied it. A group we never touched
|
|
249
|
+
// (no `hooks` array, or one that held nothing of ours) is somebody else's
|
|
250
|
+
// and is written back exactly as found — pruning is for our own entries,
|
|
251
|
+
// not a cleanup pass over the user's file.
|
|
252
|
+
const kept: ClaudeHookMatcher[] = [];
|
|
253
|
+
for (const matcher of matchers as ClaudeHookMatcher[]) {
|
|
254
|
+
if (!matcher || !Array.isArray(matcher.hooks)) {
|
|
255
|
+
if (matcher) kept.push(matcher);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const before = matcher.hooks.length;
|
|
259
|
+
matcher.hooks = matcher.hooks.filter(
|
|
260
|
+
(h) => !isMarkedHook(h as Record<string, unknown>),
|
|
261
|
+
);
|
|
262
|
+
const weEmptiedIt = matcher.hooks.length === 0 && before > 0;
|
|
263
|
+
if (!weEmptiedIt) kept.push(matcher);
|
|
264
|
+
}
|
|
265
|
+
s.hooks[eventType] = kept;
|
|
266
|
+
if (kept.length === 0) delete s.hooks[eventType];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for (const eventType of CLAUDE_INSTALL_EVENT_TYPES) {
|
|
200
270
|
const hookEntry = this.buildHookEntry(binaryPath, eventType, scope) as unknown as ClaudeHookEntry;
|
|
201
271
|
if (!s.hooks[eventType]) s.hooks[eventType] = [];
|
|
202
272
|
const matchers: ClaudeHookMatcher[] = s.hooks[eventType];
|
|
@@ -1315,10 +1385,19 @@ export const hermes: Integration = {
|
|
|
1315
1385
|
scopes: HERMES_HOOK_SCOPES,
|
|
1316
1386
|
eventTypes: HERMES_HOOK_EVENT_TYPES,
|
|
1317
1387
|
|
|
1318
|
-
// Hermes config is USER-scope only (
|
|
1319
|
-
// project/local file, so scope/cwd are irrelevant here.
|
|
1388
|
+
// Hermes config is USER-scope only (`<HERMES_HOME>/config.yaml`); there is no
|
|
1389
|
+
// project/local file, so scope/cwd are irrelevant here. This is the DEFAULT
|
|
1390
|
+
// profile's config — see getSettingsPaths for the rest.
|
|
1320
1391
|
getSettingsPath() {
|
|
1321
|
-
return resolve(
|
|
1392
|
+
return resolve(hermesRoot(), "config.yaml");
|
|
1393
|
+
},
|
|
1394
|
+
|
|
1395
|
+
// Every profile is a separate Hermes home with its own config.yaml, so a
|
|
1396
|
+
// single-file install would leave non-default profiles running unhooked —
|
|
1397
|
+
// silently, since Hermes never reports a missing hook. Install/uninstall
|
|
1398
|
+
// therefore cover them all.
|
|
1399
|
+
getSettingsPaths() {
|
|
1400
|
+
return listHermesProfiles().map((p) => resolve(p.home, "config.yaml"));
|
|
1322
1401
|
},
|
|
1323
1402
|
|
|
1324
1403
|
readSettings(settingsPath) {
|
|
@@ -1403,21 +1482,13 @@ export const hermes: Integration = {
|
|
|
1403
1482
|
return removed;
|
|
1404
1483
|
},
|
|
1405
1484
|
|
|
1485
|
+
// "Installed" means EVERY profile is hooked, not just some. A profile created
|
|
1486
|
+
// after install has no hooks, and reporting green there would hide exactly the
|
|
1487
|
+
// silent-unenforcement gap this integration exists to close; reporting "not
|
|
1488
|
+
// installed" instead prompts a re-install, which writes the missing profiles.
|
|
1406
1489
|
hooksInstalledInSettings(scope, cwd) {
|
|
1407
|
-
const
|
|
1408
|
-
|
|
1409
|
-
try {
|
|
1410
|
-
const doc = readYamlDoc(settingsPath);
|
|
1411
|
-
const js = (doc.toJS() ?? {}) as { hooks?: Record<string, HermesHookEntry[]> };
|
|
1412
|
-
const hooks = js.hooks;
|
|
1413
|
-
if (!hooks || typeof hooks !== "object") return false;
|
|
1414
|
-
for (const entries of Object.values(hooks)) {
|
|
1415
|
-
if (Array.isArray(entries) && entries.some((h) => isMarkedHook(h))) return true;
|
|
1416
|
-
}
|
|
1417
|
-
} catch {
|
|
1418
|
-
// Corrupt config — treat as not installed.
|
|
1419
|
-
}
|
|
1420
|
-
return false;
|
|
1490
|
+
const paths = settingsPathsFor(this, scope, cwd);
|
|
1491
|
+
return paths.length > 0 && paths.every((p) => hermesConfigHasHooks(p));
|
|
1421
1492
|
},
|
|
1422
1493
|
|
|
1423
1494
|
detectInstalled() {
|
|
@@ -1425,6 +1496,39 @@ export const hermes: Integration = {
|
|
|
1425
1496
|
},
|
|
1426
1497
|
};
|
|
1427
1498
|
|
|
1499
|
+
function hermesConfigHasHooks(settingsPath: string): boolean {
|
|
1500
|
+
if (!existsSync(settingsPath)) return false;
|
|
1501
|
+
try {
|
|
1502
|
+
const doc = readYamlDoc(settingsPath);
|
|
1503
|
+
const js = (doc.toJS() ?? {}) as { hooks?: Record<string, HermesHookEntry[]> };
|
|
1504
|
+
const hooks = js.hooks;
|
|
1505
|
+
if (!hooks || typeof hooks !== "object") return false;
|
|
1506
|
+
for (const entries of Object.values(hooks)) {
|
|
1507
|
+
if (Array.isArray(entries) && entries.some((h) => isMarkedHook(h))) return true;
|
|
1508
|
+
}
|
|
1509
|
+
} catch {
|
|
1510
|
+
// Corrupt config — treat as not installed.
|
|
1511
|
+
}
|
|
1512
|
+
return false;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* Profiles that exist but have no failproofai hooks — i.e. Hermes instances
|
|
1517
|
+
* currently running unenforced. Empty when Hermes isn't installed at all.
|
|
1518
|
+
*
|
|
1519
|
+
* `listHermesProfiles()` always seeds a `default` entry so install has a path to
|
|
1520
|
+
* write even on a fresh machine, so this filters to homes that actually exist —
|
|
1521
|
+
* otherwise every machine WITHOUT Hermes would report one unhooked profile and
|
|
1522
|
+
* the wizard would replace its accurate "not on PATH" hint with a phantom
|
|
1523
|
+
* warning.
|
|
1524
|
+
*/
|
|
1525
|
+
export function unhookedHermesProfiles(): string[] {
|
|
1526
|
+
return listHermesProfiles()
|
|
1527
|
+
.filter((p) => existsSync(p.home))
|
|
1528
|
+
.filter((p) => !hermesConfigHasHooks(resolve(p.home, "config.yaml")))
|
|
1529
|
+
.map((p) => p.name);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1428
1532
|
// ── OpenClaw integration ────────────────────────────────────────────────────
|
|
1429
1533
|
//
|
|
1430
1534
|
// OpenClaw is a self-hosted assistant gateway. Enforcement is via its in-process
|
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 { 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
|
|
|
@@ -261,12 +262,16 @@ async function installHooksImpl(
|
|
|
261
262
|
const writtenSettingsPaths: { cli: IntegrationType; path: string }[] = [];
|
|
262
263
|
for (const cliId of selectedClis) {
|
|
263
264
|
const integration = getIntegration(cliId);
|
|
264
|
-
|
|
265
|
+
// Usually one path; Hermes returns one per profile (each is a separate home
|
|
266
|
+
// dir with its own config.yaml, and a missed one runs unhooked in silence).
|
|
267
|
+
const settingsPaths = settingsPathsFor(integration, scope, cwd);
|
|
265
268
|
try {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
for (const settingsPath of settingsPaths) {
|
|
270
|
+
const settings = integration.readSettings(settingsPath);
|
|
271
|
+
integration.writeHookEntries(settings, binaryPath, scope);
|
|
272
|
+
integration.writeSettings(settingsPath, settings);
|
|
273
|
+
writtenSettingsPaths.push({ cli: cliId, path: settingsPath });
|
|
274
|
+
}
|
|
270
275
|
} catch (err) {
|
|
271
276
|
const errorType = err instanceof Error && /EACCES|EPERM/.test(err.message)
|
|
272
277
|
? "permission_denied"
|
|
@@ -452,9 +457,11 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
452
457
|
: [];
|
|
453
458
|
|
|
454
459
|
for (const s of scopesToRemove) {
|
|
455
|
-
|
|
460
|
+
// Usually one path; Hermes returns one per profile.
|
|
461
|
+
const settingsPaths = settingsPathsFor(integration, s, cwd);
|
|
462
|
+
const existing = settingsPaths.filter((p) => existsSync(p));
|
|
456
463
|
|
|
457
|
-
if (
|
|
464
|
+
if (existing.length === 0) {
|
|
458
465
|
if (scope !== "all" && selectedClis.length === 1) {
|
|
459
466
|
console.log("No settings file found. Nothing to remove.");
|
|
460
467
|
nothingToReport = true;
|
|
@@ -462,18 +469,22 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
462
469
|
continue;
|
|
463
470
|
}
|
|
464
471
|
|
|
465
|
-
|
|
466
|
-
|
|
472
|
+
let removedHere = 0;
|
|
473
|
+
for (const settingsPath of existing) {
|
|
474
|
+
const removed = integration.removeHooksFromFile(settingsPath);
|
|
475
|
+
removedHere += removed;
|
|
476
|
+
if (removed > 0 && scope !== "all") {
|
|
477
|
+
console.log(`Removed ${removed} failproofai hook(s) from ${integration.displayName} settings.`);
|
|
478
|
+
console.log(`Settings: ${settingsPath}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (removedHere === 0 && scope !== "all" && selectedClis.length === 1) {
|
|
467
483
|
console.log("No hooks found in settings. Nothing to remove.");
|
|
468
484
|
nothingToReport = true;
|
|
469
485
|
continue;
|
|
470
486
|
}
|
|
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
|
-
}
|
|
487
|
+
totalRemoved += removedHere;
|
|
477
488
|
}
|
|
478
489
|
}
|
|
479
490
|
|
|
@@ -484,7 +495,9 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
|
|
|
484
495
|
for (const cliId of selectedClis) {
|
|
485
496
|
const integration = getIntegration(cliId);
|
|
486
497
|
for (const s of integration.scopes) {
|
|
487
|
-
|
|
498
|
+
for (const p of settingsPathsFor(integration, s, cwd)) {
|
|
499
|
+
console.log(` ${integration.displayName} / ${s}: ${p}`);
|
|
500
|
+
}
|
|
488
501
|
}
|
|
489
502
|
}
|
|
490
503
|
}
|
|
@@ -696,33 +709,86 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
696
709
|
}
|
|
697
710
|
|
|
698
711
|
// Convention Policies section (.failproofai/policies/*policies.{js,mjs,ts})
|
|
699
|
-
|
|
712
|
+
// Walk up to the project root like enforcement does
|
|
713
|
+
// (custom-hooks-loader -> findProjectConfigDir); resolving at the exact cwd
|
|
714
|
+
// meant running this from a subdirectory listed no project policies while the
|
|
715
|
+
// hook path was loading them.
|
|
716
|
+
const base = findProjectConfigDir(cwd ?? process.cwd());
|
|
717
|
+
const projectDir = resolve(base, ".failproofai", "policies");
|
|
718
|
+
const userDir = resolve(homedir(), ".failproofai", "policies");
|
|
719
|
+
const sameDir = userDir === projectDir;
|
|
700
720
|
const conventionDirs: { label: string; dir: string }[] = [
|
|
701
|
-
{ label: "Project"
|
|
702
|
-
|
|
721
|
+
{ label: sameDir ? "Project + User" : "Project", dir: projectDir },
|
|
722
|
+
// Running from $HOME makes both paths identical. Listing the directory
|
|
723
|
+
// twice printed every file a second time as "failed to load" — the file was
|
|
724
|
+
// already imported by the first pass, so the module cache short-circuits
|
|
725
|
+
// `customPolicies.add` and `loadCustomHooks` legitimately returns 0 hooks.
|
|
726
|
+
// Nothing was wrong with the policy; the second listing was.
|
|
727
|
+
...(sameDir ? [] : [{ label: "User", dir: userDir }]),
|
|
703
728
|
];
|
|
704
729
|
|
|
730
|
+
// Record of what was found, mirrored into policies-config.json below so the
|
|
731
|
+
// config shows installed convention policies and not only enabled builtins.
|
|
732
|
+
const discovered: Record<"project" | "user", ConventionPolicyRecord[]> = {
|
|
733
|
+
project: [],
|
|
734
|
+
user: [],
|
|
735
|
+
};
|
|
736
|
+
|
|
705
737
|
for (const { label, dir } of conventionDirs) {
|
|
706
738
|
const files = discoverPolicyFiles(dir);
|
|
707
739
|
if (files.length === 0) continue;
|
|
708
740
|
|
|
741
|
+
// A shared project/user directory is listed once but belongs to both, so
|
|
742
|
+
// its record is written to both scopes' config files.
|
|
743
|
+
const targets: ("project" | "user")[] =
|
|
744
|
+
dir === projectDir && dir === userDir ? ["project", "user"] : dir === projectDir ? ["project"] : ["user"];
|
|
745
|
+
const record = (file: string, hooks: string[]) => {
|
|
746
|
+
for (const t of targets) discovered[t].push({ file, hooks });
|
|
747
|
+
};
|
|
748
|
+
|
|
709
749
|
console.log(`\n \u2500\u2500 Convention Policies \u2014 ${label} (${dir}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
750
|
+
// `nameColWidth` is sized to the longest BUILTIN name, but a convention
|
|
751
|
+
// filename can be longer \u2014 `padEnd` is then a no-op and the hook count runs
|
|
752
|
+
// straight into the filename with no space
|
|
753
|
+
// (`enforce-bengaluru-event-links-policies.mjs1 hook(s)`). Widen to fit the
|
|
754
|
+
// files actually being printed, keeping a two-space gutter.
|
|
755
|
+
const colWidth = Math.max(nameColWidth, ...files.map((f) => basename(f).length + 2));
|
|
710
756
|
for (const file of files) {
|
|
711
757
|
try {
|
|
712
758
|
const hooks = await loadCustomHooks(file);
|
|
759
|
+
const filename = basename(file);
|
|
760
|
+
record(filename, hooks.map((h) => h.name));
|
|
713
761
|
if (hooks.length === 0) {
|
|
714
|
-
|
|
715
|
-
console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
|
|
762
|
+
console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(colWidth)}\x1B[31mfailed to load\x1B[0m`);
|
|
716
763
|
} else {
|
|
717
|
-
const filename = basename(file);
|
|
718
764
|
const hookSummary = hooks.map((h) => h.name).join(", ");
|
|
719
|
-
console.log(` \x1B[32m\u2713\x1B[0m ${filename.padEnd(
|
|
765
|
+
console.log(` \x1B[32m\u2713\x1B[0m ${filename.padEnd(colWidth)}${hooks.length} hook(s): ${hookSummary}`);
|
|
720
766
|
}
|
|
721
767
|
} catch {
|
|
722
768
|
const filename = basename(file);
|
|
723
|
-
|
|
769
|
+
record(filename, []);
|
|
770
|
+
console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(colWidth)}\x1B[31merror\x1B[0m`);
|
|
724
771
|
}
|
|
725
772
|
}
|
|
726
773
|
console.log();
|
|
727
774
|
}
|
|
775
|
+
|
|
776
|
+
// Mirror what was just listed into the USER config. Safe here because
|
|
777
|
+
// `failproofai policies` is a one-shot command — never do this on the hook
|
|
778
|
+
// path (see the HooksConfig.conventionPolicies doc comment).
|
|
779
|
+
//
|
|
780
|
+
// User scope only, deliberately. A project's `.failproofai/policies-config.json`
|
|
781
|
+
// is routinely committed (this repo tracks its own), so writing the record
|
|
782
|
+
// there would make a plain `failproofai policies` dirty the working tree and
|
|
783
|
+
// put a spurious diff in front of every contributor — a read command must not
|
|
784
|
+
// do that. `discovered.project` is still collected so the listing above can
|
|
785
|
+
// report it; it simply is not persisted.
|
|
786
|
+
try {
|
|
787
|
+
syncConventionPolicies(discovered.user, "user");
|
|
788
|
+
} catch (err) {
|
|
789
|
+
// Listing must never fail because the mirror could not be written.
|
|
790
|
+
hookLogWarn(
|
|
791
|
+
`could not record convention policies in config: ${err instanceof Error ? err.message : String(err)}`,
|
|
792
|
+
);
|
|
793
|
+
}
|
|
728
794
|
}
|
|
@@ -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).
|
|
@@ -93,4 +93,30 @@ export interface HooksConfig {
|
|
|
93
93
|
* Only ever written explicitly as `false`.
|
|
94
94
|
*/
|
|
95
95
|
customPoliciesEnabled?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Record of the convention policies (`.failproofai/policies/*policies.mjs`)
|
|
98
|
+
* discovered at this scope the last time a failproofai command ran.
|
|
99
|
+
*
|
|
100
|
+
* DESCRIPTIVE, NEVER AUTHORITATIVE. Enforcement always discovers from the
|
|
101
|
+
* filesystem (`loadAllCustomHooks`) and never reads this key — a policy file
|
|
102
|
+
* dropped in enforces on the very next tool call whether or not this list has
|
|
103
|
+
* caught up. Making the loader trust it would mean a freshly-copied policy
|
|
104
|
+
* silently doing nothing until some command refreshed the config, which is
|
|
105
|
+
* the exact silent-non-enforcement failure this project exists to remove.
|
|
106
|
+
*
|
|
107
|
+
* Refreshed wholesale (not merged) by `failproofai policies` and the setup
|
|
108
|
+
* wizard, so a deleted file disappears from the record on the next run. It is
|
|
109
|
+
* deliberately NOT written from the hook path: that runs on every tool call,
|
|
110
|
+
* and a read-modify-write from concurrent short-lived hook processes with no
|
|
111
|
+
* locking would corrupt the file that governs enforcement.
|
|
112
|
+
*/
|
|
113
|
+
conventionPolicies?: ConventionPolicyRecord[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** One convention policy file and the hooks it registered, as last observed. */
|
|
117
|
+
export interface ConventionPolicyRecord {
|
|
118
|
+
/** Basename, e.g. `hermes-guardrails.policies.mjs`. */
|
|
119
|
+
file: string;
|
|
120
|
+
/** Hook names the file registered, or `[]` if it failed to load. */
|
|
121
|
+
hooks: string[];
|
|
96
122
|
}
|
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 {
|