failproofai 0.0.2 → 0.0.3
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/.claude/settings.json +316 -0
- package/.next/standalone/.failproofai/policies/workflow-policies.mjs +62 -0
- package/.next/standalone/.failproofai/policies-config.json +39 -0
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +3 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- 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.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 +2 -2
- 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.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 +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
- 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 +11 -11
- 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/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- 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 +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- 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 +2 -2
- 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.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 +2 -2
- 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.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 +2 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- 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]__0g72weg._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kjo7d_._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_05pz9._._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__12kr5~_._.js → [root-of-the-server]__03kiqd5._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qo8503._.js → [root-of-the-server]__0bo8s~-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +9 -9
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0a_7sdg.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0ef3uwk.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0j79~gv.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0pbja1x.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0r6o0i2.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_11y81~_.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_12or2kf.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/{0x-625~1vx1lu.js → 02t9.s735hqyq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ov60i6md~37t.js → 03oepxbqx6o8~.js} +2 -2
- package/.next/standalone/.next/static/chunks/{031pa5~qfzt~_.js → 09e7drilkf1sn.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0y~0creqvl5wx.js → 0cwft44dh9bww.js} +1 -1
- package/.next/standalone/.next/static/chunks/{06og.7e9nkpjh.js → 0e2uz2g026ckb.js} +1 -1
- package/.next/standalone/.next/static/chunks/0gu_a.a80ritd.css +1 -0
- package/.next/standalone/.next/static/chunks/{15wf7x-e.8ia3.js → 0h5kbvg~.xf.v.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0_4y_t03jn2nq.js → 0od..umlku4bb.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0cvffh-pbsv5u.js → 0xbwzy4dl87-0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0c_ljlxa._4lc.js → 18cl6wups7ouq.js} +2 -2
- package/.next/standalone/.next/static/chunks/{turbopack-0uc5y~g6h.n7-.js → turbopack-0r26pc8h0y_-e.js} +1 -1
- package/.next/standalone/CHANGELOG.md +103 -0
- package/.next/standalone/CLAUDE.md +28 -0
- package/.next/standalone/Dockerfile.docs +12 -0
- package/.next/standalone/README.md +95 -49
- package/.next/standalone/app/components/session-hooks-panel.tsx +14 -1
- package/.next/standalone/app/policies/hooks-client.tsx +14 -1
- package/.next/standalone/bin/failproofai.mjs +5 -0
- package/.next/standalone/bun.lock +76 -63
- package/.next/standalone/components/navbar.tsx +5 -0
- package/.next/standalone/dist/cli.mjs +539 -90
- package/.next/standalone/dist/index.js +2 -2
- package/.next/standalone/docs/ar/architecture.mdx +333 -0
- package/.next/standalone/docs/ar/built-in-policies.mdx +566 -0
- package/.next/standalone/docs/ar/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/ar/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/ar/cli/hook.mdx +31 -0
- package/.next/standalone/docs/ar/cli/install-policies.mdx +49 -0
- package/.next/standalone/docs/ar/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/ar/cli/remove-policies.mdx +45 -0
- package/.next/standalone/docs/ar/cli/version.mdx +13 -0
- package/.next/standalone/docs/ar/configuration.mdx +223 -0
- package/.next/standalone/docs/ar/custom-policies.mdx +359 -0
- package/.next/standalone/docs/ar/dashboard.mdx +142 -0
- package/.next/standalone/docs/ar/examples.mdx +254 -0
- package/.next/standalone/docs/ar/for-agents.mdx +39 -0
- package/.next/standalone/docs/ar/getting-started.mdx +134 -0
- package/.next/standalone/docs/ar/introduction.mdx +58 -0
- package/.next/standalone/docs/ar/package-aliases.mdx +82 -0
- package/.next/standalone/docs/ar/testing.mdx +261 -0
- package/.next/standalone/docs/{architecture.md → architecture.mdx} +40 -23
- package/.next/standalone/docs/{built-in-policies.md → built-in-policies.mdx} +151 -13
- package/.next/standalone/docs/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/cli/hook.mdx +30 -0
- package/.next/standalone/docs/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/cli/version.mdx +12 -0
- package/.next/standalone/docs/{configuration.md → configuration.mdx} +62 -16
- package/.next/standalone/docs/custom-policies.mdx +357 -0
- package/.next/standalone/docs/{dashboard.md → dashboard.mdx} +26 -29
- package/.next/standalone/docs/de/architecture.mdx +332 -0
- package/.next/standalone/docs/de/built-in-policies.mdx +564 -0
- package/.next/standalone/docs/de/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/de/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/de/cli/hook.mdx +30 -0
- package/.next/standalone/docs/de/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/de/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/de/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/de/cli/version.mdx +12 -0
- package/.next/standalone/docs/de/configuration.mdx +222 -0
- package/.next/standalone/docs/de/custom-policies.mdx +357 -0
- package/.next/standalone/docs/de/dashboard.mdx +142 -0
- package/.next/standalone/docs/de/examples.mdx +253 -0
- package/.next/standalone/docs/de/for-agents.mdx +38 -0
- package/.next/standalone/docs/de/getting-started.mdx +134 -0
- package/.next/standalone/docs/de/introduction.mdx +57 -0
- package/.next/standalone/docs/de/package-aliases.mdx +82 -0
- package/.next/standalone/docs/de/testing.mdx +260 -0
- package/.next/standalone/docs/docs.json +938 -24
- package/.next/standalone/docs/es/architecture.mdx +332 -0
- package/.next/standalone/docs/es/built-in-policies.mdx +564 -0
- package/.next/standalone/docs/es/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/es/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/es/cli/hook.mdx +30 -0
- package/.next/standalone/docs/es/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/es/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/es/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/es/cli/version.mdx +12 -0
- package/.next/standalone/docs/es/configuration.mdx +222 -0
- package/.next/standalone/docs/es/custom-policies.mdx +357 -0
- package/.next/standalone/docs/es/dashboard.mdx +142 -0
- package/.next/standalone/docs/es/examples.mdx +253 -0
- package/.next/standalone/docs/es/for-agents.mdx +38 -0
- package/.next/standalone/docs/es/getting-started.mdx +134 -0
- package/.next/standalone/docs/es/introduction.mdx +57 -0
- package/.next/standalone/docs/es/package-aliases.mdx +82 -0
- package/.next/standalone/docs/es/testing.mdx +260 -0
- package/.next/standalone/docs/examples.mdx +253 -0
- package/.next/standalone/docs/for-agents.mdx +38 -0
- package/.next/standalone/docs/fr/architecture.mdx +332 -0
- package/.next/standalone/docs/fr/built-in-policies.mdx +564 -0
- package/.next/standalone/docs/fr/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/fr/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/fr/cli/hook.mdx +30 -0
- package/.next/standalone/docs/fr/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/fr/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/fr/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/fr/cli/version.mdx +12 -0
- package/.next/standalone/docs/fr/configuration.mdx +222 -0
- package/.next/standalone/docs/fr/custom-policies.mdx +357 -0
- package/.next/standalone/docs/fr/dashboard.mdx +142 -0
- package/.next/standalone/docs/fr/examples.mdx +253 -0
- package/.next/standalone/docs/fr/for-agents.mdx +38 -0
- package/.next/standalone/docs/fr/getting-started.mdx +134 -0
- package/.next/standalone/docs/fr/introduction.mdx +57 -0
- package/.next/standalone/docs/fr/package-aliases.mdx +82 -0
- package/.next/standalone/docs/fr/testing.mdx +260 -0
- package/.next/standalone/docs/getting-started.mdx +134 -0
- package/.next/standalone/docs/he/architecture.mdx +333 -0
- package/.next/standalone/docs/he/built-in-policies.mdx +564 -0
- package/.next/standalone/docs/he/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/he/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/he/cli/hook.mdx +30 -0
- package/.next/standalone/docs/he/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/he/cli/list-policies.mdx +32 -0
- package/.next/standalone/docs/he/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/he/cli/version.mdx +12 -0
- package/.next/standalone/docs/he/configuration.mdx +222 -0
- package/.next/standalone/docs/he/custom-policies.mdx +357 -0
- package/.next/standalone/docs/he/dashboard.mdx +142 -0
- package/.next/standalone/docs/he/examples.mdx +253 -0
- package/.next/standalone/docs/he/for-agents.mdx +38 -0
- package/.next/standalone/docs/he/getting-started.mdx +135 -0
- package/.next/standalone/docs/he/introduction.mdx +57 -0
- package/.next/standalone/docs/he/package-aliases.mdx +82 -0
- package/.next/standalone/docs/he/testing.mdx +260 -0
- package/.next/standalone/docs/hi/architecture.mdx +334 -0
- package/.next/standalone/docs/hi/built-in-policies.mdx +564 -0
- package/.next/standalone/docs/hi/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/hi/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/hi/cli/hook.mdx +30 -0
- package/.next/standalone/docs/hi/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/hi/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/hi/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/hi/cli/version.mdx +12 -0
- package/.next/standalone/docs/hi/configuration.mdx +222 -0
- package/.next/standalone/docs/hi/custom-policies.mdx +357 -0
- package/.next/standalone/docs/hi/dashboard.mdx +142 -0
- package/.next/standalone/docs/hi/examples.mdx +255 -0
- package/.next/standalone/docs/hi/for-agents.mdx +38 -0
- package/.next/standalone/docs/hi/getting-started.mdx +134 -0
- package/.next/standalone/docs/hi/introduction.mdx +57 -0
- package/.next/standalone/docs/hi/package-aliases.mdx +82 -0
- package/.next/standalone/docs/hi/testing.mdx +260 -0
- package/.next/standalone/docs/i18n/README.ar.md +312 -0
- package/.next/standalone/docs/i18n/README.de.md +307 -0
- package/.next/standalone/docs/i18n/README.es.md +307 -0
- package/.next/standalone/docs/i18n/README.fr.md +307 -0
- package/.next/standalone/docs/i18n/README.he.md +312 -0
- package/.next/standalone/docs/i18n/README.hi.md +307 -0
- package/.next/standalone/docs/i18n/README.it.md +307 -0
- package/.next/standalone/docs/i18n/README.ja.md +307 -0
- package/.next/standalone/docs/i18n/README.ko.md +307 -0
- package/.next/standalone/docs/i18n/README.pt-br.md +307 -0
- package/.next/standalone/docs/i18n/README.ru.md +308 -0
- package/.next/standalone/docs/i18n/README.tr.md +308 -0
- package/.next/standalone/docs/i18n/README.vi.md +308 -0
- package/.next/standalone/docs/i18n/README.zh.md +307 -0
- package/.next/standalone/docs/introduction.mdx +57 -0
- package/.next/standalone/docs/it/architecture.mdx +333 -0
- package/.next/standalone/docs/it/built-in-policies.mdx +564 -0
- package/.next/standalone/docs/it/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/it/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/it/cli/hook.mdx +30 -0
- package/.next/standalone/docs/it/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/it/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/it/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/it/cli/version.mdx +12 -0
- package/.next/standalone/docs/it/configuration.mdx +223 -0
- package/.next/standalone/docs/it/custom-policies.mdx +358 -0
- package/.next/standalone/docs/it/dashboard.mdx +142 -0
- package/.next/standalone/docs/it/examples.mdx +253 -0
- package/.next/standalone/docs/it/for-agents.mdx +38 -0
- package/.next/standalone/docs/it/getting-started.mdx +134 -0
- package/.next/standalone/docs/it/introduction.mdx +57 -0
- package/.next/standalone/docs/it/package-aliases.mdx +82 -0
- package/.next/standalone/docs/it/testing.mdx +260 -0
- package/.next/standalone/docs/ja/architecture.mdx +332 -0
- package/.next/standalone/docs/ja/built-in-policies.mdx +562 -0
- package/.next/standalone/docs/ja/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/ja/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/ja/cli/hook.mdx +30 -0
- package/.next/standalone/docs/ja/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/ja/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/ja/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/ja/cli/version.mdx +12 -0
- package/.next/standalone/docs/ja/configuration.mdx +222 -0
- package/.next/standalone/docs/ja/custom-policies.mdx +357 -0
- package/.next/standalone/docs/ja/dashboard.mdx +142 -0
- package/.next/standalone/docs/ja/examples.mdx +253 -0
- package/.next/standalone/docs/ja/for-agents.mdx +38 -0
- package/.next/standalone/docs/ja/getting-started.mdx +134 -0
- package/.next/standalone/docs/ja/introduction.mdx +57 -0
- package/.next/standalone/docs/ja/package-aliases.mdx +82 -0
- package/.next/standalone/docs/ja/testing.mdx +260 -0
- package/.next/standalone/docs/ko/architecture.mdx +332 -0
- package/.next/standalone/docs/ko/built-in-policies.mdx +562 -0
- package/.next/standalone/docs/ko/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/ko/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/ko/cli/hook.mdx +30 -0
- package/.next/standalone/docs/ko/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/ko/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/ko/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/ko/cli/version.mdx +12 -0
- package/.next/standalone/docs/ko/configuration.mdx +222 -0
- package/.next/standalone/docs/ko/custom-policies.mdx +357 -0
- package/.next/standalone/docs/ko/dashboard.mdx +142 -0
- package/.next/standalone/docs/ko/examples.mdx +253 -0
- package/.next/standalone/docs/ko/for-agents.mdx +38 -0
- package/.next/standalone/docs/ko/getting-started.mdx +134 -0
- package/.next/standalone/docs/ko/introduction.mdx +57 -0
- package/.next/standalone/docs/ko/package-aliases.mdx +82 -0
- package/.next/standalone/docs/ko/testing.mdx +260 -0
- package/.next/standalone/docs/logo/dark.svg +21 -0
- package/.next/standalone/docs/logo/light.svg +21 -0
- package/.next/standalone/docs/{package-aliases.md → package-aliases.mdx} +5 -5
- package/.next/standalone/docs/pt-br/architecture.mdx +332 -0
- package/.next/standalone/docs/pt-br/built-in-policies.mdx +564 -0
- package/.next/standalone/docs/pt-br/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/pt-br/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/pt-br/cli/hook.mdx +30 -0
- package/.next/standalone/docs/pt-br/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/pt-br/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/pt-br/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/pt-br/cli/version.mdx +12 -0
- package/.next/standalone/docs/pt-br/configuration.mdx +222 -0
- package/.next/standalone/docs/pt-br/custom-policies.mdx +357 -0
- package/.next/standalone/docs/pt-br/dashboard.mdx +142 -0
- package/.next/standalone/docs/pt-br/examples.mdx +253 -0
- package/.next/standalone/docs/pt-br/for-agents.mdx +38 -0
- package/.next/standalone/docs/pt-br/getting-started.mdx +134 -0
- package/.next/standalone/docs/pt-br/introduction.mdx +57 -0
- package/.next/standalone/docs/pt-br/package-aliases.mdx +82 -0
- package/.next/standalone/docs/pt-br/testing.mdx +260 -0
- package/.next/standalone/docs/ru/architecture.mdx +334 -0
- package/.next/standalone/docs/ru/built-in-policies.mdx +562 -0
- package/.next/standalone/docs/ru/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/ru/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/ru/cli/hook.mdx +30 -0
- package/.next/standalone/docs/ru/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/ru/cli/list-policies.mdx +32 -0
- package/.next/standalone/docs/ru/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/ru/cli/version.mdx +12 -0
- package/.next/standalone/docs/ru/configuration.mdx +223 -0
- package/.next/standalone/docs/ru/custom-policies.mdx +357 -0
- package/.next/standalone/docs/ru/dashboard.mdx +142 -0
- package/.next/standalone/docs/ru/examples.mdx +254 -0
- package/.next/standalone/docs/ru/for-agents.mdx +38 -0
- package/.next/standalone/docs/ru/getting-started.mdx +134 -0
- package/.next/standalone/docs/ru/introduction.mdx +57 -0
- package/.next/standalone/docs/ru/package-aliases.mdx +82 -0
- package/.next/standalone/docs/ru/testing.mdx +260 -0
- package/.next/standalone/docs/{testing.md → testing.mdx} +11 -11
- package/.next/standalone/docs/tr/architecture.mdx +333 -0
- package/.next/standalone/docs/tr/built-in-policies.mdx +562 -0
- package/.next/standalone/docs/tr/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/tr/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/tr/cli/hook.mdx +30 -0
- package/.next/standalone/docs/tr/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/tr/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/tr/cli/remove-policies.mdx +45 -0
- package/.next/standalone/docs/tr/cli/version.mdx +12 -0
- package/.next/standalone/docs/tr/configuration.mdx +223 -0
- package/.next/standalone/docs/tr/custom-policies.mdx +357 -0
- package/.next/standalone/docs/tr/dashboard.mdx +142 -0
- package/.next/standalone/docs/tr/examples.mdx +253 -0
- package/.next/standalone/docs/tr/for-agents.mdx +38 -0
- package/.next/standalone/docs/tr/getting-started.mdx +134 -0
- package/.next/standalone/docs/tr/introduction.mdx +57 -0
- package/.next/standalone/docs/tr/package-aliases.mdx +82 -0
- package/.next/standalone/docs/tr/testing.mdx +260 -0
- package/.next/standalone/docs/vi/architecture.mdx +333 -0
- package/.next/standalone/docs/vi/built-in-policies.mdx +564 -0
- package/.next/standalone/docs/vi/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/vi/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/vi/cli/hook.mdx +30 -0
- package/.next/standalone/docs/vi/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/vi/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/vi/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/vi/cli/version.mdx +13 -0
- package/.next/standalone/docs/vi/configuration.mdx +222 -0
- package/.next/standalone/docs/vi/custom-policies.mdx +357 -0
- package/.next/standalone/docs/vi/dashboard.mdx +142 -0
- package/.next/standalone/docs/vi/examples.mdx +253 -0
- package/.next/standalone/docs/vi/for-agents.mdx +38 -0
- package/.next/standalone/docs/vi/getting-started.mdx +134 -0
- package/.next/standalone/docs/vi/introduction.mdx +57 -0
- package/.next/standalone/docs/vi/package-aliases.mdx +82 -0
- package/.next/standalone/docs/vi/testing.mdx +260 -0
- package/.next/standalone/docs/zh/architecture.mdx +332 -0
- package/.next/standalone/docs/zh/built-in-policies.mdx +562 -0
- package/.next/standalone/docs/zh/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/zh/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/zh/cli/hook.mdx +30 -0
- package/.next/standalone/docs/zh/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/zh/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/zh/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/zh/cli/version.mdx +12 -0
- package/.next/standalone/docs/zh/configuration.mdx +222 -0
- package/.next/standalone/docs/zh/custom-policies.mdx +357 -0
- package/.next/standalone/docs/zh/dashboard.mdx +142 -0
- package/.next/standalone/docs/zh/examples.mdx +253 -0
- package/.next/standalone/docs/zh/for-agents.mdx +38 -0
- package/.next/standalone/docs/zh/getting-started.mdx +134 -0
- package/.next/standalone/docs/zh/introduction.mdx +57 -0
- package/.next/standalone/docs/zh/package-aliases.mdx +82 -0
- package/.next/standalone/docs/zh/testing.mdx +260 -0
- package/.next/standalone/examples/convention-policies/security-policies.mjs +40 -0
- package/.next/standalone/examples/convention-policies/workflow-policies.mjs +41 -0
- package/.next/standalone/next.config.ts +5 -3
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/jsonwebtoken/index.js +2 -2
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- 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 +7 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/render.js +20 -19
- package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +15 -15
- package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
- package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
- package/.next/standalone/node_modules/react/package.json +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/package.json +2 -2
- package/.next/standalone/package.json +13 -10
- package/.next/standalone/scripts/translate-docs/cache.ts +62 -0
- package/.next/standalone/scripts/translate-docs/cli.ts +357 -0
- package/.next/standalone/scripts/translate-docs/config.ts +248 -0
- package/.next/standalone/scripts/translate-docs/mdx-translator.ts +153 -0
- package/.next/standalone/scripts/translate-docs/mintlify-nav.ts +107 -0
- package/.next/standalone/scripts/translate-docs/readme-translator.ts +154 -0
- package/.next/standalone/scripts/translate-docs/translator.ts +68 -0
- package/.next/standalone/scripts/translate-docs/types.ts +43 -0
- package/.next/standalone/server.js +1 -1
- package/.next/standalone/skills-lock.json +10 -0
- package/.next/standalone/src/hooks/builtin-policies.ts +405 -25
- package/.next/standalone/src/hooks/custom-hooks-loader.ts +165 -21
- package/.next/standalone/src/hooks/handler.ts +33 -6
- package/.next/standalone/src/hooks/hook-activity-store.ts +6 -1
- package/.next/standalone/src/hooks/hooks-config.ts +47 -2
- package/.next/standalone/src/hooks/llm-client.ts +2 -2
- package/.next/standalone/src/hooks/loader-utils.ts +4 -4
- package/.next/standalone/src/hooks/manager.ts +67 -16
- package/.next/standalone/src/hooks/policy-evaluator.ts +58 -19
- package/.next/standalone/src/hooks/policy-helpers.ts +2 -2
- package/.next/standalone/vitest.config.e2e.mts +3 -0
- package/.next/standalone/vitest.config.mts +3 -0
- package/README.md +95 -49
- package/bin/failproofai.mjs +5 -0
- package/dist/cli.mjs +539 -90
- package/dist/index.js +2 -2
- package/package.json +13 -10
- package/scripts/translate-docs/cache.ts +62 -0
- package/scripts/translate-docs/cli.ts +357 -0
- package/scripts/translate-docs/config.ts +248 -0
- package/scripts/translate-docs/mdx-translator.ts +153 -0
- package/scripts/translate-docs/mintlify-nav.ts +107 -0
- package/scripts/translate-docs/readme-translator.ts +154 -0
- package/scripts/translate-docs/translator.ts +68 -0
- package/scripts/translate-docs/types.ts +43 -0
- package/src/hooks/builtin-policies.ts +405 -25
- package/src/hooks/custom-hooks-loader.ts +165 -21
- package/src/hooks/handler.ts +33 -6
- package/src/hooks/hook-activity-store.ts +6 -1
- package/src/hooks/hooks-config.ts +47 -2
- package/src/hooks/llm-client.ts +2 -2
- package/src/hooks/loader-utils.ts +4 -4
- package/src/hooks/manager.ts +67 -16
- package/src/hooks/policy-evaluator.ts +58 -19
- package/src/hooks/policy-helpers.ts +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__02nt~6d._.js +0 -3
- package/.next/standalone/.next/static/chunks/15jpradyu_531.css +0 -1
- package/.next/standalone/docs/cli-reference.md +0 -175
- package/.next/standalone/docs/custom-hooks.md +0 -261
- package/.next/standalone/docs/getting-started.md +0 -128
- package/.next/standalone/docs/introduction.md +0 -47
- /package/.next/standalone/.next/static/{WS-OQSqL1Lp1w_obXfjvl → En9eEShUkUjgeYbY9v6Gy}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{WS-OQSqL1Lp1w_obXfjvl → En9eEShUkUjgeYbY9v6Gy}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{WS-OQSqL1Lp1w_obXfjvl → En9eEShUkUjgeYbY9v6Gy}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -149,11 +149,19 @@ function readMergedHooksConfig(cwd) {
|
|
|
149
149
|
...llm !== undefined ? { llm } : {}
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
152
|
+
function getConfigPathForScope(scope, cwd) {
|
|
153
|
+
const base = cwd ? resolve(cwd) : process.cwd();
|
|
154
|
+
switch (scope) {
|
|
155
|
+
case "user":
|
|
156
|
+
return resolve(homedir2(), ".failproofai", "policies-config.json");
|
|
157
|
+
case "project":
|
|
158
|
+
return resolve(base, ".failproofai", "policies-config.json");
|
|
159
|
+
case "local":
|
|
160
|
+
return resolve(base, ".failproofai", "policies-config.local.json");
|
|
161
|
+
}
|
|
154
162
|
}
|
|
155
|
-
function
|
|
156
|
-
const configPath =
|
|
163
|
+
function readScopedHooksConfig(scope, cwd) {
|
|
164
|
+
const configPath = getConfigPathForScope(scope, cwd);
|
|
157
165
|
if (!existsSync2(configPath)) {
|
|
158
166
|
return { enabledPolicies: [] };
|
|
159
167
|
}
|
|
@@ -165,8 +173,8 @@ function readHooksConfig() {
|
|
|
165
173
|
return { enabledPolicies: [] };
|
|
166
174
|
}
|
|
167
175
|
}
|
|
168
|
-
function
|
|
169
|
-
const configPath =
|
|
176
|
+
function writeScopedHooksConfig(config, scope, cwd) {
|
|
177
|
+
const configPath = getConfigPathForScope(scope, cwd);
|
|
170
178
|
const dir = dirname(configPath);
|
|
171
179
|
if (!existsSync2(dir)) {
|
|
172
180
|
mkdirSync2(dir, { recursive: true });
|
|
@@ -179,8 +187,8 @@ var init_hooks_config = __esm(() => {
|
|
|
179
187
|
});
|
|
180
188
|
|
|
181
189
|
// src/hooks/policy-helpers.ts
|
|
182
|
-
function allow() {
|
|
183
|
-
return { decision: "allow" };
|
|
190
|
+
function allow(reason) {
|
|
191
|
+
return reason ? { decision: "allow", reason } : { decision: "allow" };
|
|
184
192
|
}
|
|
185
193
|
function deny(reason) {
|
|
186
194
|
return { decision: "deny", reason };
|
|
@@ -248,7 +256,7 @@ var REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__", INDEX_CACHE_KEY = "__FAILP
|
|
|
248
256
|
// src/hooks/builtin-policies.ts
|
|
249
257
|
import { resolve as resolve2, join as join2 } from "node:path";
|
|
250
258
|
import { readFile, writeFile } from "node:fs/promises";
|
|
251
|
-
import { execSync } from "node:child_process";
|
|
259
|
+
import { execSync, execFileSync } from "node:child_process";
|
|
252
260
|
import { homedir as homedir3 } from "node:os";
|
|
253
261
|
function isClaudeInternalPath(resolved2) {
|
|
254
262
|
const claudeDir = join2(homedir3(), ".claude");
|
|
@@ -266,6 +274,53 @@ function getFilePath(ctx) {
|
|
|
266
274
|
function parseArgvTokens(cmd) {
|
|
267
275
|
return cmd.trim().split(/\s+/).map((t) => t.replace(/^['"]|['"]$/g, ""));
|
|
268
276
|
}
|
|
277
|
+
function getCurrentBranch(cwd) {
|
|
278
|
+
try {
|
|
279
|
+
let branch = gitBranchCache.get(cwd);
|
|
280
|
+
if (branch === undefined) {
|
|
281
|
+
branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
282
|
+
cwd,
|
|
283
|
+
encoding: "utf8",
|
|
284
|
+
timeout: 3000
|
|
285
|
+
}).trim();
|
|
286
|
+
gitBranchCache.set(cwd, branch);
|
|
287
|
+
}
|
|
288
|
+
return branch || null;
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function getHeadSha(cwd) {
|
|
294
|
+
try {
|
|
295
|
+
const sha = execSync("git rev-parse HEAD", {
|
|
296
|
+
cwd,
|
|
297
|
+
encoding: "utf8",
|
|
298
|
+
timeout: 3000
|
|
299
|
+
}).trim();
|
|
300
|
+
return sha || null;
|
|
301
|
+
} catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function getThirdPartyCheckRuns(cwd, sha) {
|
|
306
|
+
try {
|
|
307
|
+
const json = execFileSync("gh", [
|
|
308
|
+
"api",
|
|
309
|
+
`repos/{owner}/{repo}/commits/${sha}/check-runs`,
|
|
310
|
+
"--jq",
|
|
311
|
+
'.check_runs | map(select(.app.slug != "github-actions")) | map({name: .name, status: .status, conclusion: (.conclusion // "")})'
|
|
312
|
+
], {
|
|
313
|
+
cwd,
|
|
314
|
+
encoding: "utf8",
|
|
315
|
+
timeout: 15000
|
|
316
|
+
}).trim();
|
|
317
|
+
if (!json || json === "[]")
|
|
318
|
+
return [];
|
|
319
|
+
return JSON.parse(json);
|
|
320
|
+
} catch {
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
269
324
|
function matchesAllowedPattern(cmd, pattern) {
|
|
270
325
|
const cmdTokens = parseArgvTokens(cmd);
|
|
271
326
|
const patTokens = parseArgvTokens(pattern);
|
|
@@ -480,7 +535,8 @@ function rmTargetIsAllowed(cmd, allowPaths) {
|
|
|
480
535
|
if (rmIdx < 0)
|
|
481
536
|
continue;
|
|
482
537
|
const flagTokens = tokens.slice(rmIdx + 1).filter((t) => /^-[^-]/.test(t));
|
|
483
|
-
|
|
538
|
+
const longFlagsInSeg = tokens.slice(rmIdx + 1).filter((t) => /^--/.test(t));
|
|
539
|
+
if (!/r/i.test(flagTokens.join("")) && !longFlagsInSeg.some((f) => /^--recursive$/i.test(f)))
|
|
484
540
|
continue;
|
|
485
541
|
const pathArgs = tokens.slice(rmIdx + 1).filter((t) => !t.startsWith("-"));
|
|
486
542
|
for (const target of pathArgs) {
|
|
@@ -505,7 +561,10 @@ function blockRmRf(ctx) {
|
|
|
505
561
|
if (ctx.toolName !== "Bash")
|
|
506
562
|
return allow();
|
|
507
563
|
const cmd = getCommand(ctx);
|
|
508
|
-
const hasDestructivePath =
|
|
564
|
+
const hasDestructivePath = parseArgvTokens(cmd).some((token) => {
|
|
565
|
+
const normalized = token.replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
|
|
566
|
+
return normalized === "/" || normalized === "~" || /^\/[A-Za-z_][\w.-]*$/.test(normalized);
|
|
567
|
+
});
|
|
509
568
|
if (hasDestructivePath && (/rm\s+-[^\s]*r[^\s]*f[^\s]*/.test(cmd) || /rm\s+-[^\s]*f[^\s]*r[^\s]*/.test(cmd))) {
|
|
510
569
|
const allowPaths = ctx.params?.allowPaths ?? [];
|
|
511
570
|
if (rmTargetIsAllowed(cmd, allowPaths))
|
|
@@ -515,7 +574,10 @@ function blockRmRf(ctx) {
|
|
|
515
574
|
if (hasDestructivePath && /\brm\b/.test(cmd)) {
|
|
516
575
|
const tokens = parseArgvTokens(cmd);
|
|
517
576
|
const shortFlags = tokens.filter((t) => /^-[^-]/.test(t)).join("");
|
|
518
|
-
|
|
577
|
+
const longFlags = tokens.filter((t) => /^--/.test(t));
|
|
578
|
+
const hasRecursive = /r/i.test(shortFlags) || longFlags.some((f) => /^--recursive$/i.test(f));
|
|
579
|
+
const hasForce = /f/.test(shortFlags) || longFlags.some((f) => /^--force$/i.test(f));
|
|
580
|
+
if (hasRecursive && hasForce) {
|
|
519
581
|
const allowPaths = ctx.params?.allowPaths ?? [];
|
|
520
582
|
if (rmTargetIsAllowed(cmd, allowPaths))
|
|
521
583
|
return allow();
|
|
@@ -653,22 +715,12 @@ function blockWorkOnMain(ctx) {
|
|
|
653
715
|
const cwd = ctx.session?.cwd;
|
|
654
716
|
if (!cwd)
|
|
655
717
|
return allow();
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if (branch === undefined) {
|
|
659
|
-
branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
660
|
-
cwd,
|
|
661
|
-
encoding: "utf8",
|
|
662
|
-
timeout: 3000
|
|
663
|
-
}).trim();
|
|
664
|
-
gitBranchCache.set(cwd, branch);
|
|
665
|
-
}
|
|
666
|
-
const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
|
|
667
|
-
if (protectedBranches.includes(branch)) {
|
|
668
|
-
return deny(`Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`);
|
|
669
|
-
}
|
|
670
|
-
} catch {
|
|
718
|
+
const branch = getCurrentBranch(cwd);
|
|
719
|
+
if (!branch)
|
|
671
720
|
return allow();
|
|
721
|
+
const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
|
|
722
|
+
if (protectedBranches.includes(branch)) {
|
|
723
|
+
return deny(`Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`);
|
|
672
724
|
}
|
|
673
725
|
return allow();
|
|
674
726
|
}
|
|
@@ -767,6 +819,170 @@ function warnBackgroundProcess(ctx) {
|
|
|
767
819
|
}
|
|
768
820
|
return allow();
|
|
769
821
|
}
|
|
822
|
+
function requireCommitBeforeStop(ctx) {
|
|
823
|
+
const cwd = ctx.session?.cwd;
|
|
824
|
+
if (!cwd)
|
|
825
|
+
return allow("No working directory available, skipping commit check.");
|
|
826
|
+
try {
|
|
827
|
+
const status = execSync("git status --porcelain", {
|
|
828
|
+
cwd,
|
|
829
|
+
encoding: "utf8",
|
|
830
|
+
timeout: 5000
|
|
831
|
+
}).trim();
|
|
832
|
+
if (status.length > 0) {
|
|
833
|
+
return deny("You have uncommitted changes in the working directory. Commit all changes before stopping.");
|
|
834
|
+
}
|
|
835
|
+
return allow("All changes are committed.");
|
|
836
|
+
} catch {
|
|
837
|
+
return allow("Not a git repository, skipping commit check.");
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
function requirePushBeforeStop(ctx) {
|
|
841
|
+
const cwd = ctx.session?.cwd;
|
|
842
|
+
if (!cwd)
|
|
843
|
+
return allow("No working directory available, skipping push check.");
|
|
844
|
+
try {
|
|
845
|
+
const remotes = execSync("git remote", {
|
|
846
|
+
cwd,
|
|
847
|
+
encoding: "utf8",
|
|
848
|
+
timeout: 3000
|
|
849
|
+
}).trim();
|
|
850
|
+
if (!remotes)
|
|
851
|
+
return allow("No git remote configured, skipping push check.");
|
|
852
|
+
const remote = ctx.params?.remote ?? "origin";
|
|
853
|
+
const branch = getCurrentBranch(cwd);
|
|
854
|
+
if (!branch || branch === "HEAD")
|
|
855
|
+
return allow("Detached HEAD, skipping push check.");
|
|
856
|
+
const baseBranch = ctx.params?.baseBranch ?? "main";
|
|
857
|
+
if (branch === baseBranch) {
|
|
858
|
+
return allow(`On base branch "${baseBranch}", skipping push check.`);
|
|
859
|
+
}
|
|
860
|
+
try {
|
|
861
|
+
const ahead = execFileSync("git", ["log", `${remote}/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
862
|
+
if (!ahead) {
|
|
863
|
+
return allow(`No commits ahead of ${remote}/${baseBranch}, skipping push check.`);
|
|
864
|
+
}
|
|
865
|
+
const diff = execFileSync("git", ["diff", "--stat", `${remote}/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
866
|
+
if (!diff) {
|
|
867
|
+
return allow(`No file changes compared to ${remote}/${baseBranch}, skipping push check.`);
|
|
868
|
+
}
|
|
869
|
+
} catch {}
|
|
870
|
+
let hasTracking = false;
|
|
871
|
+
try {
|
|
872
|
+
execFileSync("git", ["rev-parse", "--verify", `${remote}/${branch}`], {
|
|
873
|
+
cwd,
|
|
874
|
+
encoding: "utf8",
|
|
875
|
+
timeout: 3000
|
|
876
|
+
});
|
|
877
|
+
hasTracking = true;
|
|
878
|
+
} catch {}
|
|
879
|
+
if (!hasTracking) {
|
|
880
|
+
return deny(`Branch "${branch}" has not been pushed to remote "${remote}". ` + `Push your branch with: git push -u ${remote} ${branch}`);
|
|
881
|
+
}
|
|
882
|
+
const unpushed = execFileSync("git", ["log", `${remote}/${branch}..HEAD`, "--oneline"], {
|
|
883
|
+
cwd,
|
|
884
|
+
encoding: "utf8",
|
|
885
|
+
timeout: 5000
|
|
886
|
+
}).trim();
|
|
887
|
+
if (unpushed.length > 0) {
|
|
888
|
+
const commitCount = unpushed.split(`
|
|
889
|
+
`).length;
|
|
890
|
+
return deny(`You have ${commitCount} unpushed commit${commitCount > 1 ? "s" : ""} on branch "${branch}". ` + `Push your changes with: git push`);
|
|
891
|
+
}
|
|
892
|
+
return allow(`All commits pushed to "${remote}".`);
|
|
893
|
+
} catch {
|
|
894
|
+
return allow("Could not check push status, skipping.");
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function requirePrBeforeStop(ctx) {
|
|
898
|
+
const cwd = ctx.session?.cwd;
|
|
899
|
+
if (!cwd)
|
|
900
|
+
return allow("No working directory available, skipping PR check.");
|
|
901
|
+
try {
|
|
902
|
+
try {
|
|
903
|
+
execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
|
|
904
|
+
} catch {
|
|
905
|
+
return allow("GitHub CLI (gh) not installed, skipping PR check.");
|
|
906
|
+
}
|
|
907
|
+
const branch = getCurrentBranch(cwd);
|
|
908
|
+
if (!branch || branch === "HEAD")
|
|
909
|
+
return allow("Detached HEAD, skipping PR check.");
|
|
910
|
+
const baseBranch = ctx.params?.baseBranch ?? "main";
|
|
911
|
+
if (branch === baseBranch) {
|
|
912
|
+
return allow(`On base branch "${baseBranch}", skipping PR check.`);
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
const ahead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
916
|
+
if (!ahead) {
|
|
917
|
+
return allow(`No commits ahead of origin/${baseBranch}, skipping PR check.`);
|
|
918
|
+
}
|
|
919
|
+
const diff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
|
|
920
|
+
if (!diff) {
|
|
921
|
+
return allow(`No file changes compared to origin/${baseBranch}, skipping PR check.`);
|
|
922
|
+
}
|
|
923
|
+
} catch {}
|
|
924
|
+
let prJson;
|
|
925
|
+
try {
|
|
926
|
+
prJson = execSync("gh pr view --json number,url,state", {
|
|
927
|
+
cwd,
|
|
928
|
+
encoding: "utf8",
|
|
929
|
+
timeout: 15000
|
|
930
|
+
}).trim();
|
|
931
|
+
} catch {
|
|
932
|
+
return deny(`No pull request found for branch "${branch}". ` + `Create one with: gh pr create`);
|
|
933
|
+
}
|
|
934
|
+
const pr = JSON.parse(prJson);
|
|
935
|
+
if (pr.state === "OPEN") {
|
|
936
|
+
return allow(`PR #${pr.number} exists: ${pr.url}`);
|
|
937
|
+
}
|
|
938
|
+
return deny(`Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Create a new PR with: gh pr create`);
|
|
939
|
+
} catch {
|
|
940
|
+
return allow("Could not check PR status, skipping.");
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function requireCiGreenBeforeStop(ctx) {
|
|
944
|
+
const cwd = ctx.session?.cwd;
|
|
945
|
+
if (!cwd)
|
|
946
|
+
return allow("No working directory available, skipping CI check.");
|
|
947
|
+
try {
|
|
948
|
+
try {
|
|
949
|
+
execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
|
|
950
|
+
} catch {
|
|
951
|
+
return allow("GitHub CLI (gh) not installed, skipping CI check.");
|
|
952
|
+
}
|
|
953
|
+
const branch = getCurrentBranch(cwd);
|
|
954
|
+
if (!branch || branch === "HEAD")
|
|
955
|
+
return allow("Detached HEAD, skipping CI check.");
|
|
956
|
+
let workflowRuns = [];
|
|
957
|
+
try {
|
|
958
|
+
const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"], { cwd, encoding: "utf8", timeout: 15000 }).trim();
|
|
959
|
+
if (runsJson && runsJson !== "[]") {
|
|
960
|
+
workflowRuns = JSON.parse(runsJson);
|
|
961
|
+
}
|
|
962
|
+
} catch {}
|
|
963
|
+
let thirdPartyChecks = [];
|
|
964
|
+
const sha = getHeadSha(cwd);
|
|
965
|
+
if (sha) {
|
|
966
|
+
thirdPartyChecks = getThirdPartyCheckRuns(cwd, sha);
|
|
967
|
+
}
|
|
968
|
+
const allChecks = [...workflowRuns, ...thirdPartyChecks];
|
|
969
|
+
if (allChecks.length === 0)
|
|
970
|
+
return allow(`No CI runs found for branch "${branch}".`);
|
|
971
|
+
const failing = allChecks.filter((r) => r.status === "completed" && r.conclusion !== "success" && r.conclusion !== "skipped");
|
|
972
|
+
if (failing.length > 0) {
|
|
973
|
+
const names = failing.map((r) => `"${r.name}"`).join(", ");
|
|
974
|
+
return deny(`CI checks are failing on branch "${branch}": ${names}. Fix the failing checks before stopping.`);
|
|
975
|
+
}
|
|
976
|
+
const pending = allChecks.filter((r) => r.status === "in_progress" || r.status === "queued" || r.status === "waiting");
|
|
977
|
+
if (pending.length > 0) {
|
|
978
|
+
const names = pending.map((r) => `"${r.name}"`).join(", ");
|
|
979
|
+
return deny(`CI checks are still running on branch "${branch}": ${names}. Wait for all checks to complete and verify they pass.`);
|
|
980
|
+
}
|
|
981
|
+
return allow(`All CI checks passed on branch "${branch}".`);
|
|
982
|
+
} catch {
|
|
983
|
+
return allow("Could not check CI status, skipping.");
|
|
984
|
+
}
|
|
985
|
+
}
|
|
770
986
|
function registerBuiltinPolicies(enabledNames) {
|
|
771
987
|
const enabledSet = new Set(enabledNames);
|
|
772
988
|
for (const policy of BUILTIN_POLICIES) {
|
|
@@ -802,7 +1018,7 @@ var init_builtin_policies = __esm(() => {
|
|
|
802
1018
|
SCHEMA_ALTER_RE = /\bALTER\s+TABLE\b[\s\S]*\b(?:DROP\s+COLUMN|ADD\s+COLUMN|RENAME\s+(?:COLUMN|TO)|MODIFY\s+COLUMN)\b/i;
|
|
803
1019
|
PUBLISH_CMD_RE = /(?:npm\s+publish|bun\s+publish|pnpm\s+publish|yarn\s+npm\s+publish|twine\s+upload|poetry\s+publish|cargo\s+publish|gem\s+push)\b/;
|
|
804
1020
|
ENV_PRINTENV_RE = /(?:^|\s|;|&&|\|\|)(?:env|printenv)(?:\s|$|;|&&|\|)/;
|
|
805
|
-
ECHO_ENV_RE = /echo\s
|
|
1021
|
+
ECHO_ENV_RE = /echo\s+.*\$\{?[A-Za-z_]/;
|
|
806
1022
|
EXPORT_RE = /(?:^|\s|;|&&|\|\|)export\s+\w+/;
|
|
807
1023
|
PS_ENV_VAR_RE = /\$env:[A-Za-z_]/i;
|
|
808
1024
|
PS_CHILDITEM_ENV_RE = /(?:Get-ChildItem|dir|gci|ls)\s+Env:/i;
|
|
@@ -813,7 +1029,7 @@ var init_builtin_policies = __esm(() => {
|
|
|
813
1029
|
SUDO_RE = /(?:^|;|&&|\|\|)\s*sudo\s/;
|
|
814
1030
|
PS_ELEVATION_RE = /Start-Process\s+.*-Verb\s+RunAs/i;
|
|
815
1031
|
RUNAS_RE = /(?:^|;|&&|\|\|)\s*runas\s/i;
|
|
816
|
-
CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh)/;
|
|
1032
|
+
CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh|dash|ksh|csh|tcsh|fish|ash)\b/;
|
|
817
1033
|
PS_WEB_PIPE_RE = /(?:Invoke-WebRequest|iwr|Invoke-RestMethod|irm)\s+.*\|\s*(?:Invoke-Expression|iex)/i;
|
|
818
1034
|
FORCE_PUSH_RE = /(?:--force|-f\b)/;
|
|
819
1035
|
SECRET_FILE_RE = /\.(?:pem|key)$/;
|
|
@@ -1102,11 +1318,75 @@ var init_builtin_policies = __esm(() => {
|
|
|
1102
1318
|
match: { events: ["PreToolUse"] },
|
|
1103
1319
|
defaultEnabled: false,
|
|
1104
1320
|
category: "AI Behavior"
|
|
1321
|
+
},
|
|
1322
|
+
{
|
|
1323
|
+
name: "require-commit-before-stop",
|
|
1324
|
+
description: "Require all changes to be committed before Claude stops",
|
|
1325
|
+
fn: requireCommitBeforeStop,
|
|
1326
|
+
match: { events: ["Stop"] },
|
|
1327
|
+
defaultEnabled: false,
|
|
1328
|
+
category: "Workflow",
|
|
1329
|
+
beta: true
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
name: "require-push-before-stop",
|
|
1333
|
+
description: "Require all commits to be pushed to remote before Claude stops",
|
|
1334
|
+
fn: requirePushBeforeStop,
|
|
1335
|
+
match: { events: ["Stop"] },
|
|
1336
|
+
defaultEnabled: false,
|
|
1337
|
+
category: "Workflow",
|
|
1338
|
+
beta: true,
|
|
1339
|
+
params: {
|
|
1340
|
+
remote: {
|
|
1341
|
+
type: "string",
|
|
1342
|
+
description: "Remote name to push to (default: origin)",
|
|
1343
|
+
default: "origin"
|
|
1344
|
+
},
|
|
1345
|
+
baseBranch: {
|
|
1346
|
+
type: "string",
|
|
1347
|
+
description: "Base branch to compare against (default: main)",
|
|
1348
|
+
default: "main"
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
name: "require-pr-before-stop",
|
|
1354
|
+
description: "Require a pull request to exist for the current branch before Claude stops",
|
|
1355
|
+
fn: requirePrBeforeStop,
|
|
1356
|
+
match: { events: ["Stop"] },
|
|
1357
|
+
defaultEnabled: false,
|
|
1358
|
+
category: "Workflow",
|
|
1359
|
+
beta: true,
|
|
1360
|
+
params: {
|
|
1361
|
+
baseBranch: {
|
|
1362
|
+
type: "string",
|
|
1363
|
+
description: "Base branch to compare against (default: main)",
|
|
1364
|
+
default: "main"
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
name: "require-ci-green-before-stop",
|
|
1370
|
+
description: "Require CI checks to pass on the current branch before Claude stops",
|
|
1371
|
+
fn: requireCiGreenBeforeStop,
|
|
1372
|
+
match: { events: ["Stop"] },
|
|
1373
|
+
defaultEnabled: false,
|
|
1374
|
+
category: "Workflow",
|
|
1375
|
+
beta: true
|
|
1105
1376
|
}
|
|
1106
1377
|
];
|
|
1107
1378
|
});
|
|
1108
1379
|
|
|
1109
1380
|
// src/hooks/policy-evaluator.ts
|
|
1381
|
+
function appendHint(baseReason, hint) {
|
|
1382
|
+
const base = baseReason.trim();
|
|
1383
|
+
const normalizedHint = typeof hint === "string" ? hint.trim() : "";
|
|
1384
|
+
if (!normalizedHint)
|
|
1385
|
+
return base;
|
|
1386
|
+
if (!base)
|
|
1387
|
+
return normalizedHint;
|
|
1388
|
+
return `${base}. ${normalizedHint}`;
|
|
1389
|
+
}
|
|
1110
1390
|
async function evaluatePolicies(eventType, payload, session, config) {
|
|
1111
1391
|
const toolName = payload.tool_name;
|
|
1112
1392
|
const toolInput = payload.tool_input;
|
|
@@ -1122,8 +1402,8 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1122
1402
|
toolInput,
|
|
1123
1403
|
session
|
|
1124
1404
|
};
|
|
1125
|
-
|
|
1126
|
-
|
|
1405
|
+
const instructEntries = [];
|
|
1406
|
+
const allowEntries = [];
|
|
1127
1407
|
for (const policy of policies) {
|
|
1128
1408
|
const schema = POLICY_PARAMS_MAP.get(policy.name);
|
|
1129
1409
|
let ctx;
|
|
@@ -1145,7 +1425,7 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1145
1425
|
continue;
|
|
1146
1426
|
}
|
|
1147
1427
|
if (result.decision === "deny") {
|
|
1148
|
-
const reason = result.reason ?? `Blocked by policy: ${policy.name}
|
|
1428
|
+
const reason = appendHint(result.reason ?? `Blocked by policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
|
|
1149
1429
|
hookLogInfo(`deny by "${policy.name}": ${reason}`);
|
|
1150
1430
|
const displayTool = ctx.toolName ?? "unknown tool";
|
|
1151
1431
|
if (eventType === "PreToolUse") {
|
|
@@ -1184,44 +1464,63 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1184
1464
|
return {
|
|
1185
1465
|
exitCode: 2,
|
|
1186
1466
|
stdout: "",
|
|
1187
|
-
stderr:
|
|
1467
|
+
stderr: reason,
|
|
1188
1468
|
policyName: policy.name,
|
|
1189
1469
|
reason,
|
|
1190
1470
|
decision: "deny"
|
|
1191
1471
|
};
|
|
1192
1472
|
}
|
|
1193
|
-
if (result.decision === "instruct"
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
hookLogInfo(`instruct by "${policy.name}": ${
|
|
1473
|
+
if (result.decision === "instruct") {
|
|
1474
|
+
const reason = appendHint(result.reason ?? `Instruction from policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
|
|
1475
|
+
instructEntries.push({ policyName: policy.name, reason });
|
|
1476
|
+
hookLogInfo(`instruct by "${policy.name}": ${reason}`);
|
|
1477
|
+
}
|
|
1478
|
+
if (result.decision === "allow" && result.reason) {
|
|
1479
|
+
allowEntries.push({ policyName: policy.name, reason: result.reason });
|
|
1197
1480
|
}
|
|
1198
1481
|
}
|
|
1199
|
-
if (
|
|
1482
|
+
if (instructEntries.length > 0) {
|
|
1483
|
+
const combined = instructEntries.map((e) => e.reason).join(`
|
|
1484
|
+
`);
|
|
1485
|
+
const policyNames = instructEntries.map((e) => e.policyName);
|
|
1200
1486
|
if (eventType === "Stop") {
|
|
1201
1487
|
return {
|
|
1202
1488
|
exitCode: 2,
|
|
1203
1489
|
stdout: "",
|
|
1204
|
-
stderr:
|
|
1205
|
-
policyName:
|
|
1206
|
-
|
|
1490
|
+
stderr: combined,
|
|
1491
|
+
policyName: policyNames[0],
|
|
1492
|
+
policyNames,
|
|
1493
|
+
reason: combined,
|
|
1207
1494
|
decision: "instruct"
|
|
1208
1495
|
};
|
|
1209
1496
|
}
|
|
1210
1497
|
const response = {
|
|
1211
1498
|
hookSpecificOutput: {
|
|
1212
1499
|
hookEventName: eventType,
|
|
1213
|
-
additionalContext: `Instruction from failproofai: ${
|
|
1500
|
+
additionalContext: `Instruction from failproofai: ${combined}`
|
|
1214
1501
|
}
|
|
1215
1502
|
};
|
|
1216
1503
|
return {
|
|
1217
1504
|
exitCode: 0,
|
|
1218
1505
|
stdout: JSON.stringify(response),
|
|
1219
1506
|
stderr: "",
|
|
1220
|
-
policyName:
|
|
1221
|
-
|
|
1507
|
+
policyName: policyNames[0],
|
|
1508
|
+
policyNames,
|
|
1509
|
+
reason: combined,
|
|
1222
1510
|
decision: "instruct"
|
|
1223
1511
|
};
|
|
1224
1512
|
}
|
|
1513
|
+
if (allowEntries.length > 0) {
|
|
1514
|
+
const combined = allowEntries.map((e) => e.reason).join(`
|
|
1515
|
+
`);
|
|
1516
|
+
const policyNames = allowEntries.map((e) => e.policyName);
|
|
1517
|
+
const supportsHookSpecificOutput = eventType === "PreToolUse" || eventType === "PostToolUse" || eventType === "UserPromptSubmit";
|
|
1518
|
+
const response = supportsHookSpecificOutput ? { hookSpecificOutput: { hookEventName: eventType, additionalContext: `Note from failproofai: ${combined}` } } : { reason: combined };
|
|
1519
|
+
const stderrMsg = allowEntries.map((e) => `[failproofai] ${e.policyName}: ${e.reason}`).join(`
|
|
1520
|
+
`);
|
|
1521
|
+
return { exitCode: 0, stdout: JSON.stringify(response), stderr: stderrMsg + `
|
|
1522
|
+
`, policyName: policyNames[0], policyNames, reason: combined, decision: "allow" };
|
|
1523
|
+
}
|
|
1225
1524
|
return { exitCode: 0, stdout: "", stderr: "", policyName: null, reason: null, decision: "allow" };
|
|
1226
1525
|
}
|
|
1227
1526
|
var POLICY_PARAMS_MAP;
|
|
@@ -1291,10 +1590,9 @@ async function createEsmShim(distIndex, distUrl) {
|
|
|
1291
1590
|
const shimPath = distIndex + ".__failproofai_esm_shim__.mjs";
|
|
1292
1591
|
const shimCode = [
|
|
1293
1592
|
`import _cjs from '${distUrl}';`,
|
|
1294
|
-
`export const createApp = _cjs.createApp;`,
|
|
1295
|
-
`export const getQueueCondition = _cjs.getQueueCondition;`,
|
|
1296
|
-
`export const clearQueueCondition = _cjs.clearQueueCondition;`,
|
|
1297
1593
|
`export const customPolicies = _cjs.customPolicies;`,
|
|
1594
|
+
`export const getCustomHooks = _cjs.getCustomHooks;`,
|
|
1595
|
+
`export const clearCustomHooks = _cjs.clearCustomHooks;`,
|
|
1298
1596
|
`export const allow = _cjs.allow;`,
|
|
1299
1597
|
`export const deny = _cjs.deny;`,
|
|
1300
1598
|
`export const instruct = _cjs.instruct;`,
|
|
@@ -1374,20 +1672,21 @@ var init_loader_utils = __esm(() => {
|
|
|
1374
1672
|
});
|
|
1375
1673
|
|
|
1376
1674
|
// src/hooks/custom-hooks-loader.ts
|
|
1377
|
-
import { resolve as resolve4, isAbsolute } from "node:path";
|
|
1378
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
1675
|
+
import { resolve as resolve4, isAbsolute, basename } from "node:path";
|
|
1676
|
+
import { existsSync as existsSync3, readdirSync } from "node:fs";
|
|
1379
1677
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
1380
|
-
|
|
1381
|
-
|
|
1678
|
+
import { homedir as homedir4 } from "node:os";
|
|
1679
|
+
function discoverPolicyFiles(dir) {
|
|
1680
|
+
if (!existsSync3(dir))
|
|
1382
1681
|
return [];
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
hookLogWarn(`customPoliciesPath not found: ${absPath}`);
|
|
1682
|
+
try {
|
|
1683
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1684
|
+
return entries.filter((e) => e.isFile() && CONVENTION_FILE_RE.test(e.name)).sort((a, b) => a.name.localeCompare(b.name)).map((e) => resolve4(dir, e.name));
|
|
1685
|
+
} catch {
|
|
1388
1686
|
return [];
|
|
1389
1687
|
}
|
|
1390
|
-
|
|
1688
|
+
}
|
|
1689
|
+
async function loadSingleFile(absPath, opts) {
|
|
1391
1690
|
const g = globalThis;
|
|
1392
1691
|
g[LOADING_KEY] = true;
|
|
1393
1692
|
let tmpFiles = [];
|
|
@@ -1403,18 +1702,93 @@ async function loadCustomHooks(customPoliciesPath, opts) {
|
|
|
1403
1702
|
if (opts?.strict)
|
|
1404
1703
|
throw new Error(`Failed to load custom hooks from ${absPath}: ${msg}`);
|
|
1405
1704
|
hookLogError(`failed to load custom hooks from ${absPath}: ${msg}`);
|
|
1406
|
-
return [];
|
|
1407
1705
|
} finally {
|
|
1408
1706
|
g[LOADING_KEY] = false;
|
|
1409
1707
|
await cleanupTmpFiles(tmpFiles);
|
|
1410
1708
|
}
|
|
1709
|
+
}
|
|
1710
|
+
async function loadCustomHooks(customPoliciesPath, opts) {
|
|
1711
|
+
if (!customPoliciesPath)
|
|
1712
|
+
return [];
|
|
1713
|
+
const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
|
|
1714
|
+
if (!existsSync3(absPath)) {
|
|
1715
|
+
if (opts?.strict)
|
|
1716
|
+
throw new Error(`Custom hooks file not found: ${absPath}`);
|
|
1717
|
+
hookLogWarn(`customPoliciesPath not found: ${absPath}`);
|
|
1718
|
+
return [];
|
|
1719
|
+
}
|
|
1720
|
+
clearCustomHooks();
|
|
1721
|
+
await loadSingleFile(absPath, opts);
|
|
1411
1722
|
return getCustomHooks();
|
|
1412
1723
|
}
|
|
1413
|
-
|
|
1724
|
+
async function loadAllCustomHooks(customPoliciesPath, opts) {
|
|
1725
|
+
clearCustomHooks();
|
|
1726
|
+
const conventionSources = [];
|
|
1727
|
+
if (customPoliciesPath) {
|
|
1728
|
+
const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
|
|
1729
|
+
if (existsSync3(absPath)) {
|
|
1730
|
+
await loadSingleFile(absPath);
|
|
1731
|
+
} else {
|
|
1732
|
+
hookLogWarn(`customPoliciesPath not found: ${absPath}`);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
const hooksBeforeConvention = getCustomHooks().length;
|
|
1736
|
+
const projectDir = resolve4(opts?.sessionCwd ?? process.cwd(), ".failproofai", "policies");
|
|
1737
|
+
const projectFiles = discoverPolicyFiles(projectDir);
|
|
1738
|
+
for (const file of projectFiles) {
|
|
1739
|
+
const hooksBefore = getCustomHooks().length;
|
|
1740
|
+
await loadSingleFile(file);
|
|
1741
|
+
const newHooks = getCustomHooks().slice(hooksBefore);
|
|
1742
|
+
if (newHooks.length > 0) {
|
|
1743
|
+
conventionSources.push({
|
|
1744
|
+
scope: "project",
|
|
1745
|
+
file: basename(file),
|
|
1746
|
+
hookNames: newHooks.map((h) => h.name)
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
const userDir = resolve4(homedir4(), ".failproofai", "policies");
|
|
1751
|
+
const userFiles = discoverPolicyFiles(userDir);
|
|
1752
|
+
for (const file of userFiles) {
|
|
1753
|
+
const hooksBefore = getCustomHooks().length;
|
|
1754
|
+
await loadSingleFile(file);
|
|
1755
|
+
const newHooks = getCustomHooks().slice(hooksBefore);
|
|
1756
|
+
if (newHooks.length > 0) {
|
|
1757
|
+
conventionSources.push({
|
|
1758
|
+
scope: "user",
|
|
1759
|
+
file: basename(file),
|
|
1760
|
+
hookNames: newHooks.map((h) => h.name)
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
const allHooks = getCustomHooks();
|
|
1765
|
+
const conventionCount = allHooks.length - hooksBeforeConvention;
|
|
1766
|
+
if (projectFiles.length > 0 || userFiles.length > 0) {
|
|
1767
|
+
hookLogInfo(`convention policies: ${projectFiles.length} project file(s), ${userFiles.length} user file(s), ${conventionCount} hook(s)`);
|
|
1768
|
+
}
|
|
1769
|
+
const hookNameToScope = new Map;
|
|
1770
|
+
for (const source of conventionSources) {
|
|
1771
|
+
for (const name of source.hookNames) {
|
|
1772
|
+
hookNameToScope.set(name, source.scope);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
const conventionHookRefs = new Set;
|
|
1776
|
+
for (const hook of allHooks.slice(hooksBeforeConvention)) {
|
|
1777
|
+
conventionHookRefs.add(hook);
|
|
1778
|
+
}
|
|
1779
|
+
for (const hook of allHooks) {
|
|
1780
|
+
if (conventionHookRefs.has(hook)) {
|
|
1781
|
+
hook.__conventionScope = hookNameToScope.get(hook.name) ?? "project";
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return { hooks: allHooks, conventionSources };
|
|
1785
|
+
}
|
|
1786
|
+
var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__", CONVENTION_FILE_RE;
|
|
1414
1787
|
var init_custom_hooks_loader = __esm(() => {
|
|
1415
1788
|
init_hook_logger();
|
|
1416
1789
|
init_custom_hooks_registry();
|
|
1417
1790
|
init_loader_utils();
|
|
1791
|
+
CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
|
|
1418
1792
|
});
|
|
1419
1793
|
|
|
1420
1794
|
// src/hooks/hook-activity-store.ts
|
|
@@ -1423,14 +1797,14 @@ import {
|
|
|
1423
1797
|
writeFileSync as writeFileSync2,
|
|
1424
1798
|
appendFileSync as appendFileSync2,
|
|
1425
1799
|
renameSync as renameSync2,
|
|
1426
|
-
readdirSync,
|
|
1800
|
+
readdirSync as readdirSync2,
|
|
1427
1801
|
mkdirSync as mkdirSync3,
|
|
1428
1802
|
existsSync as existsSync4,
|
|
1429
1803
|
statSync as statSync2,
|
|
1430
1804
|
unlinkSync
|
|
1431
1805
|
} from "node:fs";
|
|
1432
1806
|
import { join as join3 } from "node:path";
|
|
1433
|
-
import { homedir as
|
|
1807
|
+
import { homedir as homedir5 } from "node:os";
|
|
1434
1808
|
function ensureDir() {
|
|
1435
1809
|
if (!existsSync4(storeDir)) {
|
|
1436
1810
|
mkdirSync3(storeDir, { recursive: true });
|
|
@@ -1516,7 +1890,11 @@ function updateStats(entry) {
|
|
|
1516
1890
|
s.totalEvents += 1;
|
|
1517
1891
|
if (entry.decision === "deny")
|
|
1518
1892
|
s.denyCount += 1;
|
|
1519
|
-
if (entry.
|
|
1893
|
+
if (entry.policyNames && entry.policyNames.length > 0) {
|
|
1894
|
+
for (const name of entry.policyNames) {
|
|
1895
|
+
s.policyMap[name] = (s.policyMap[name] ?? 0) + 1;
|
|
1896
|
+
}
|
|
1897
|
+
} else if (entry.policyName) {
|
|
1520
1898
|
s.policyMap[entry.policyName] = (s.policyMap[entry.policyName] ?? 0) + 1;
|
|
1521
1899
|
}
|
|
1522
1900
|
const tmpPath = join3(storeDir, `stats.json.${process.pid}.tmp`);
|
|
@@ -1531,12 +1909,12 @@ function updateStats(entry) {
|
|
|
1531
1909
|
}
|
|
1532
1910
|
var PAGE_SIZE = 25, DEFAULT_STORE_DIR, CURRENT_FILE = "current.jsonl", COUNT_FILE = "current.count", STATS_FILE = "stats.json", LOCK_FILE = "current.lock", LOCK_STALE_MS = 2000, storeDir, rotateSeq = 0;
|
|
1533
1911
|
var init_hook_activity_store = __esm(() => {
|
|
1534
|
-
DEFAULT_STORE_DIR = join3(
|
|
1912
|
+
DEFAULT_STORE_DIR = join3(homedir5(), ".failproofai", "cache", "hook-activity");
|
|
1535
1913
|
storeDir = DEFAULT_STORE_DIR;
|
|
1536
1914
|
});
|
|
1537
1915
|
|
|
1538
1916
|
// package.json
|
|
1539
|
-
var version2 = "0.0.
|
|
1917
|
+
var version2 = "0.0.3";
|
|
1540
1918
|
var init_package = () => {};
|
|
1541
1919
|
|
|
1542
1920
|
// src/posthog-key.ts
|
|
@@ -1698,9 +2076,14 @@ async function handleHookEvent(eventType) {
|
|
|
1698
2076
|
const config = readMergedHooksConfig(session.cwd);
|
|
1699
2077
|
clearPolicies();
|
|
1700
2078
|
registerBuiltinPolicies(config.enabledPolicies);
|
|
1701
|
-
const
|
|
2079
|
+
const loadResult = await loadAllCustomHooks(config.customPoliciesPath, { sessionCwd: session.cwd });
|
|
2080
|
+
const customHooksList = loadResult.hooks;
|
|
2081
|
+
const conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
|
|
1702
2082
|
for (const hook of customHooksList) {
|
|
1703
2083
|
const hookName = hook.name;
|
|
2084
|
+
const conventionScope = hook.__conventionScope;
|
|
2085
|
+
const isConvention = !!conventionScope;
|
|
2086
|
+
const prefix = isConvention ? `.failproofai-${conventionScope}` : "custom";
|
|
1704
2087
|
const fn = async (ctx) => {
|
|
1705
2088
|
try {
|
|
1706
2089
|
const result2 = await Promise.race([
|
|
@@ -1711,16 +2094,18 @@ async function handleHookEvent(eventType) {
|
|
|
1711
2094
|
} catch (err) {
|
|
1712
2095
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1713
2096
|
const isTimeout = msg === "timeout";
|
|
1714
|
-
hookLogWarn(
|
|
2097
|
+
hookLogWarn(`${prefix} hook "${hookName}" failed: ${msg}`);
|
|
1715
2098
|
trackHookEvent(getInstanceId(), "custom_hook_error", {
|
|
1716
2099
|
hook_name: hookName,
|
|
1717
2100
|
error_type: isTimeout ? "timeout" : "exception",
|
|
1718
|
-
event_type: eventType
|
|
2101
|
+
event_type: eventType,
|
|
2102
|
+
is_convention_policy: isConvention,
|
|
2103
|
+
convention_scope: conventionScope ?? null
|
|
1719
2104
|
});
|
|
1720
2105
|
return { decision: "allow" };
|
|
1721
2106
|
}
|
|
1722
2107
|
};
|
|
1723
|
-
registerPolicy(
|
|
2108
|
+
registerPolicy(`${prefix}/${hookName}`, hook.description ?? "", fn, hook.match ?? {}, -1);
|
|
1724
2109
|
}
|
|
1725
2110
|
if (customHooksList.length > 0) {
|
|
1726
2111
|
trackHookEvent(getInstanceId(), "custom_hooks_loaded", {
|
|
@@ -1729,7 +2114,16 @@ async function handleHookEvent(eventType) {
|
|
|
1729
2114
|
event_types_covered: [...new Set(customHooksList.flatMap((h) => h.match?.events ?? []))]
|
|
1730
2115
|
});
|
|
1731
2116
|
}
|
|
1732
|
-
|
|
2117
|
+
if (loadResult.conventionSources.length > 0) {
|
|
2118
|
+
trackHookEvent(getInstanceId(), "convention_policies_loaded", {
|
|
2119
|
+
event_type: eventType,
|
|
2120
|
+
project_file_count: loadResult.conventionSources.filter((s) => s.scope === "project").length,
|
|
2121
|
+
user_file_count: loadResult.conventionSources.filter((s) => s.scope === "user").length,
|
|
2122
|
+
convention_hook_count: conventionHookNames.size,
|
|
2123
|
+
convention_hook_names: [...conventionHookNames]
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
hookLogInfo(`event=${eventType} policies=${config.enabledPolicies.length} custom=${customHooksList.length} convention=${conventionHookNames.size}`);
|
|
1733
2127
|
const result = await evaluatePolicies(eventType, parsed, session, config);
|
|
1734
2128
|
const durationMs = Math.round(performance.now() - startTime);
|
|
1735
2129
|
hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
|
|
@@ -1745,6 +2139,7 @@ async function handleHookEvent(eventType) {
|
|
|
1745
2139
|
eventType,
|
|
1746
2140
|
toolName: parsed.tool_name ?? null,
|
|
1747
2141
|
policyName: result.policyName,
|
|
2142
|
+
policyNames: result.policyNames,
|
|
1748
2143
|
decision: result.decision,
|
|
1749
2144
|
reason: result.reason,
|
|
1750
2145
|
durationMs,
|
|
@@ -1760,7 +2155,9 @@ async function handleHookEvent(eventType) {
|
|
|
1760
2155
|
if (result.decision === "deny" || result.decision === "instruct") {
|
|
1761
2156
|
try {
|
|
1762
2157
|
const isCustomHook = result.policyName?.startsWith("custom/") ?? false;
|
|
1763
|
-
const
|
|
2158
|
+
const isConventionPolicy = result.policyName?.startsWith(".failproofai-") ?? false;
|
|
2159
|
+
const conventionScope = isConventionPolicy ? result.policyName.match(/^\.failproofai-(project|user)\//)?.[1] ?? null : null;
|
|
2160
|
+
const hasCustomParams = !isCustomHook && !isConventionPolicy && !!(result.policyName && config.policyParams?.[result.policyName]);
|
|
1764
2161
|
const paramKeysOverridden = hasCustomParams ? Object.keys(config.policyParams[result.policyName]) : [];
|
|
1765
2162
|
const distinctId = getInstanceId();
|
|
1766
2163
|
await trackHookEvent(distinctId, "hook_policy_triggered", {
|
|
@@ -1769,6 +2166,8 @@ async function handleHookEvent(eventType) {
|
|
|
1769
2166
|
policy_name: result.policyName,
|
|
1770
2167
|
decision: result.decision,
|
|
1771
2168
|
is_custom_hook: isCustomHook,
|
|
2169
|
+
is_convention_policy: isConventionPolicy,
|
|
2170
|
+
convention_scope: conventionScope,
|
|
1772
2171
|
has_custom_params: hasCustomParams,
|
|
1773
2172
|
param_keys_overridden: paramKeysOverridden
|
|
1774
2173
|
});
|
|
@@ -2101,13 +2500,13 @@ __export(exports_manager, {
|
|
|
2101
2500
|
});
|
|
2102
2501
|
import { execSync as execSync3 } from "node:child_process";
|
|
2103
2502
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
|
|
2104
|
-
import { resolve as resolve5, dirname as dirname3 } from "node:path";
|
|
2105
|
-
import { homedir as
|
|
2503
|
+
import { resolve as resolve5, dirname as dirname3, basename as basename2 } from "node:path";
|
|
2504
|
+
import { homedir as homedir6, platform, arch, release, hostname } from "node:os";
|
|
2106
2505
|
function getSettingsPath(scope, cwd) {
|
|
2107
2506
|
const base = cwd ? resolve5(cwd) : process.cwd();
|
|
2108
2507
|
switch (scope) {
|
|
2109
2508
|
case "user":
|
|
2110
|
-
return resolve5(
|
|
2509
|
+
return resolve5(homedir6(), ".claude", "settings.json");
|
|
2111
2510
|
case "project":
|
|
2112
2511
|
return resolve5(base, ".claude", "settings.json");
|
|
2113
2512
|
case "local":
|
|
@@ -2234,7 +2633,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
|
|
|
2234
2633
|
}
|
|
2235
2634
|
}
|
|
2236
2635
|
const binaryPath = resolveFailproofaiBinary();
|
|
2237
|
-
const previousConfig =
|
|
2636
|
+
const previousConfig = readScopedHooksConfig(scope, cwd);
|
|
2238
2637
|
const previousEnabled = new Set(previousConfig.enabledPolicies);
|
|
2239
2638
|
let selectedPolicies;
|
|
2240
2639
|
if (policyNames !== undefined) {
|
|
@@ -2268,7 +2667,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
|
|
|
2268
2667
|
console.log(`
|
|
2269
2668
|
Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
|
|
2270
2669
|
}
|
|
2271
|
-
|
|
2670
|
+
writeScopedHooksConfig(configToWrite, scope, cwd);
|
|
2272
2671
|
console.log(`
|
|
2273
2672
|
Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`);
|
|
2274
2673
|
if (removeCustomHooks) {
|
|
@@ -2282,7 +2681,7 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
|
|
|
2282
2681
|
settings.hooks = {};
|
|
2283
2682
|
}
|
|
2284
2683
|
for (const eventType of HOOK_EVENT_TYPES) {
|
|
2285
|
-
const command = `"${binaryPath}" --hook ${eventType}`;
|
|
2684
|
+
const command = scope === "project" ? `npx -y failproofai --hook ${eventType}` : `"${binaryPath}" --hook ${eventType}`;
|
|
2286
2685
|
const hookEntry = {
|
|
2287
2686
|
type: "command",
|
|
2288
2687
|
command,
|
|
@@ -2327,12 +2726,19 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
|
|
|
2327
2726
|
hostname_hash: hashToId(hostname()),
|
|
2328
2727
|
has_custom_hooks_path: !!configToWrite.customPoliciesPath,
|
|
2329
2728
|
has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
|
|
2330
|
-
param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : []
|
|
2729
|
+
param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
|
|
2730
|
+
command_format: scope === "project" ? "npx" : "absolute"
|
|
2331
2731
|
});
|
|
2332
2732
|
} catch {}
|
|
2333
2733
|
console.log(`Failproof AI hooks installed for all ${HOOK_EVENT_TYPES.length} event types (scope: ${scope}).`);
|
|
2334
2734
|
console.log(`Settings: ${settingsPath}`);
|
|
2335
|
-
|
|
2735
|
+
if (scope === "project") {
|
|
2736
|
+
console.log(`Command: npx -y failproofai`);
|
|
2737
|
+
console.log(`
|
|
2738
|
+
This file can be committed to git — no machine-specific paths.`);
|
|
2739
|
+
} else {
|
|
2740
|
+
console.log(`Binary: ${binaryPath}`);
|
|
2741
|
+
}
|
|
2336
2742
|
const otherScopes = deduplicateScopes(HOOK_SCOPES, cwd).filter((s) => s !== scope);
|
|
2337
2743
|
const duplicates = otherScopes.filter((s) => hooksInstalledInSettings(s, cwd));
|
|
2338
2744
|
if (duplicates.length > 0) {
|
|
@@ -2345,15 +2751,16 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
|
|
|
2345
2751
|
}
|
|
2346
2752
|
}
|
|
2347
2753
|
async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
2754
|
+
const configScope = scope === "all" ? "user" : scope;
|
|
2348
2755
|
if (opts?.removeCustomHooks) {
|
|
2349
|
-
const config =
|
|
2756
|
+
const config = readScopedHooksConfig(configScope, cwd);
|
|
2350
2757
|
delete config.customPoliciesPath;
|
|
2351
|
-
|
|
2758
|
+
writeScopedHooksConfig(config, configScope, cwd);
|
|
2352
2759
|
console.log("Custom hooks path cleared.");
|
|
2353
2760
|
}
|
|
2354
2761
|
if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
|
|
2355
2762
|
validatePolicyNames(policyNames);
|
|
2356
|
-
const config =
|
|
2763
|
+
const config = readScopedHooksConfig(configScope, cwd);
|
|
2357
2764
|
const removeSet = new Set(policyNames);
|
|
2358
2765
|
const remaining = config.enabledPolicies.filter((p) => !removeSet.has(p));
|
|
2359
2766
|
const notEnabled = policyNames.filter((p) => !config.enabledPolicies.includes(p));
|
|
@@ -2367,7 +2774,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
2367
2774
|
enabledPolicies: remaining,
|
|
2368
2775
|
...filteredParams && Object.keys(filteredParams).length > 0 ? { policyParams: filteredParams } : {}
|
|
2369
2776
|
};
|
|
2370
|
-
|
|
2777
|
+
writeScopedHooksConfig(updatedConfig, configScope, cwd);
|
|
2371
2778
|
try {
|
|
2372
2779
|
const distinctId = getInstanceId();
|
|
2373
2780
|
const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
|
|
@@ -2388,7 +2795,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
2388
2795
|
console.log(`Remaining: ${remaining.length > 0 ? remaining.join(", ") : "(none)"}`);
|
|
2389
2796
|
return;
|
|
2390
2797
|
}
|
|
2391
|
-
const configBeforeRemoval =
|
|
2798
|
+
const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
|
|
2392
2799
|
const scopesToRemove = scope === "all" ? [...HOOK_SCOPES] : [scope];
|
|
2393
2800
|
let totalRemoved = 0;
|
|
2394
2801
|
for (const s of scopesToRemove) {
|
|
@@ -2435,10 +2842,18 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
2435
2842
|
hostname_hash: hashToId(hostname())
|
|
2436
2843
|
});
|
|
2437
2844
|
} catch {}
|
|
2438
|
-
if (scope === "all"
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2441
|
-
|
|
2845
|
+
if (scope === "all") {
|
|
2846
|
+
for (const s of HOOK_SCOPES) {
|
|
2847
|
+
const existing = readScopedHooksConfig(s, cwd);
|
|
2848
|
+
if (existing.enabledPolicies.length > 0 || existing.customPoliciesPath || existing.policyParams) {
|
|
2849
|
+
const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
|
|
2850
|
+
writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
} else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
|
|
2854
|
+
const existing = readScopedHooksConfig(configScope, cwd);
|
|
2855
|
+
const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
|
|
2856
|
+
writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
|
|
2442
2857
|
}
|
|
2443
2858
|
}
|
|
2444
2859
|
async function listHooks(cwd) {
|
|
@@ -2575,6 +2990,35 @@ Failproof AI Hook Policies
|
|
|
2575
2990
|
}
|
|
2576
2991
|
console.log();
|
|
2577
2992
|
}
|
|
2993
|
+
const base = cwd ? resolve5(cwd) : process.cwd();
|
|
2994
|
+
const conventionDirs = [
|
|
2995
|
+
{ label: "Project", dir: resolve5(base, ".failproofai", "policies") },
|
|
2996
|
+
{ label: "User", dir: resolve5(homedir6(), ".failproofai", "policies") }
|
|
2997
|
+
];
|
|
2998
|
+
for (const { label, dir } of conventionDirs) {
|
|
2999
|
+
const files = discoverPolicyFiles(dir);
|
|
3000
|
+
if (files.length === 0)
|
|
3001
|
+
continue;
|
|
3002
|
+
console.log(`
|
|
3003
|
+
── Convention Policies — ${label} (${dir}) ──────────`);
|
|
3004
|
+
for (const file of files) {
|
|
3005
|
+
try {
|
|
3006
|
+
const hooks = await loadCustomHooks(file);
|
|
3007
|
+
if (hooks.length === 0) {
|
|
3008
|
+
const filename = basename2(file);
|
|
3009
|
+
console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
|
|
3010
|
+
} else {
|
|
3011
|
+
const filename = basename2(file);
|
|
3012
|
+
const hookSummary = hooks.map((h) => h.name).join(", ");
|
|
3013
|
+
console.log(` \x1B[32m✓\x1B[0m ${filename.padEnd(nameColWidth)}${hooks.length} hook(s): ${hookSummary}`);
|
|
3014
|
+
}
|
|
3015
|
+
} catch {
|
|
3016
|
+
const filename = basename2(file);
|
|
3017
|
+
console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31merror\x1B[0m`);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
console.log();
|
|
3021
|
+
}
|
|
2578
3022
|
}
|
|
2579
3023
|
var VALID_POLICY_NAMES;
|
|
2580
3024
|
var init_manager = __esm(() => {
|
|
@@ -2590,10 +3034,10 @@ var init_manager = __esm(() => {
|
|
|
2590
3034
|
});
|
|
2591
3035
|
|
|
2592
3036
|
// lib/paths.ts
|
|
2593
|
-
import { homedir as
|
|
3037
|
+
import { homedir as homedir7 } from "os";
|
|
2594
3038
|
import { join as join4 } from "path";
|
|
2595
3039
|
function getDefaultClaudeProjectsPath() {
|
|
2596
|
-
return join4(
|
|
3040
|
+
return join4(homedir7(), ".claude", "projects");
|
|
2597
3041
|
}
|
|
2598
3042
|
var init_paths = () => {};
|
|
2599
3043
|
|
|
@@ -2762,7 +3206,7 @@ import { realpathSync as realpathSync2 } from "fs";
|
|
|
2762
3206
|
import { dirname as dirname5, resolve as resolve8 } from "path";
|
|
2763
3207
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2764
3208
|
// package.json
|
|
2765
|
-
var version = "0.0.
|
|
3209
|
+
var version = "0.0.3";
|
|
2766
3210
|
|
|
2767
3211
|
// bin/failproofai.mjs
|
|
2768
3212
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
@@ -2826,6 +3270,11 @@ COMMANDS
|
|
|
2826
3270
|
--version, -v Print version and exit
|
|
2827
3271
|
--help, -h Show this help message
|
|
2828
3272
|
|
|
3273
|
+
CONVENTION POLICIES
|
|
3274
|
+
Drop *policies.{js,mjs,ts} files into .failproofai/policies/ for auto-loading.
|
|
3275
|
+
Works at project level (.failproofai/policies/) and user level (~/.failproofai/policies/).
|
|
3276
|
+
No --custom flag or config changes needed \u2014 just drop files and they're picked up.
|
|
3277
|
+
|
|
2829
3278
|
EXAMPLES
|
|
2830
3279
|
failproofai policies
|
|
2831
3280
|
failproofai policies --install
|