failproofai 0.0.2-beta.7 → 0.0.2-beta.9
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 +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/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/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/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/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/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/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/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/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- 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]__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]__05zi2mt._.js → [root-of-the-server]__0vn1ciw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +2 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0kkt_9z._.js → [root-of-the-server]__0z-n~~r._.js} +2 -2
- 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/_0x..fj-._.js +1 -1
- 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/middleware-build-manifest.js +3 -3
- 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/{0ltx5i0xv85_s.js → 04wavch6dsfes.js} +1 -1
- package/.next/standalone/.next/static/chunks/{13jdpvk~s2da8.js → 0drr--vxs_m-c.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0jf9lx3rkmqx_.css → 0gu_a.a80ritd.css} +1 -1
- package/.next/standalone/.next/static/chunks/{0e76l4~hq_sei.js → 0i1ilz5554nv9.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0suauczjqzn07.js → 0keqg6-cjs8aa.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0w.rtg9.m8dk-.js → 0myzx7y.rqqi3.js} +2 -2
- package/.next/standalone/.next/static/chunks/{02u4v.k5amfah.js → 0zfrusm~j404v.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0bkizbynk9via.js → 10xhknzfyigcu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0q7atesxo-36k.js → 16yg3xhkmdb9v.js} +1 -1
- package/.next/standalone/CHANGELOG.md +27 -0
- package/.next/standalone/CLAUDE.md +14 -0
- package/.next/standalone/README.md +16 -0
- package/.next/standalone/bun.lock +45 -0
- package/.next/standalone/dist/cli.mjs +44 -23
- 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/built-in-policies.mdx +17 -1
- package/.next/standalone/docs/configuration.mdx +1 -1
- package/.next/standalone/docs/custom-policies.mdx +3 -3
- 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 +922 -35
- 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/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/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/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/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/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/package.json +8 -2
- 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/src/hooks/custom-hooks-loader.ts +12 -5
- package/.next/standalone/src/hooks/handler.ts +9 -3
- package/.next/standalone/src/hooks/manager.ts +10 -2
- package/.next/standalone/src/hooks/policy-evaluator.ts +20 -16
- package/README.md +16 -0
- package/dist/cli.mjs +44 -23
- package/package.json +8 -2
- 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/custom-hooks-loader.ts +12 -5
- package/src/hooks/handler.ts +9 -3
- package/src/hooks/manager.ts +10 -2
- package/src/hooks/policy-evaluator.ts +20 -16
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__02nt~6d._.js +0 -3
- /package/.next/standalone/.next/static/{Opbai6exOQP2W488FWmr6 → XqGmAwGDuJ6fEQgD-8y60}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{Opbai6exOQP2W488FWmr6 → XqGmAwGDuJ6fEQgD-8y60}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{Opbai6exOQP2W488FWmr6 → XqGmAwGDuJ6fEQgD-8y60}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 测试
|
|
3
|
+
description: "单元测试、端到端测试与测试辅助工具"
|
|
4
|
+
icon: flask-vial
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
failproofai 包含两套测试套件:**单元测试**(快速、使用 mock)和**端到端测试**(真实子进程调用)。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 运行测试
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 单次运行所有单元测试
|
|
15
|
+
bun run test:run
|
|
16
|
+
|
|
17
|
+
# 以监视模式运行单元测试
|
|
18
|
+
bun run test
|
|
19
|
+
|
|
20
|
+
# 运行端到端测试(需要配置环境,详见下文)
|
|
21
|
+
bun run test:e2e
|
|
22
|
+
|
|
23
|
+
# 仅进行类型检查,不构建
|
|
24
|
+
bunx tsc --noEmit
|
|
25
|
+
|
|
26
|
+
# 代码检查
|
|
27
|
+
bun run lint
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 单元测试
|
|
33
|
+
|
|
34
|
+
单元测试位于 `__tests__/` 目录,使用 [Vitest](https://vitest.dev) 和 `happy-dom`。
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
__tests__/
|
|
38
|
+
hooks/
|
|
39
|
+
builtin-policies.test.ts # 每个内置策略的逻辑测试
|
|
40
|
+
hooks-config.test.ts # 配置加载与作用域合并
|
|
41
|
+
policy-evaluator.test.ts # 参数注入与评估顺序
|
|
42
|
+
custom-hooks-registry.test.ts # globalThis 注册表的增删查
|
|
43
|
+
custom-hooks-loader.test.ts # ESM 加载器、传递性导入、错误处理
|
|
44
|
+
manager.test.ts # 安装/移除/列出操作
|
|
45
|
+
components/
|
|
46
|
+
sessions-list.test.tsx # 会话列表组件
|
|
47
|
+
project-list.test.tsx # 项目列表组件
|
|
48
|
+
...
|
|
49
|
+
lib/
|
|
50
|
+
logger.test.ts
|
|
51
|
+
paths.test.ts
|
|
52
|
+
date-filters.test.ts
|
|
53
|
+
telemetry.test.ts
|
|
54
|
+
...
|
|
55
|
+
actions/
|
|
56
|
+
get-hooks-config.test.ts
|
|
57
|
+
get-hook-activity.test.ts
|
|
58
|
+
...
|
|
59
|
+
contexts/
|
|
60
|
+
ThemeContext.test.tsx
|
|
61
|
+
AutoRefreshContext.test.tsx
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 编写策略单元测试
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
68
|
+
import { getBuiltinPolicies } from "../../src/hooks/builtin-policies";
|
|
69
|
+
import { allow, deny } from "../../src/hooks/policy-types";
|
|
70
|
+
|
|
71
|
+
describe("block-sudo", () => {
|
|
72
|
+
const policy = getBuiltinPolicies().find((p) => p.name === "block-sudo")!;
|
|
73
|
+
|
|
74
|
+
it("denies sudo commands", () => {
|
|
75
|
+
const ctx = {
|
|
76
|
+
eventType: "PreToolUse" as const,
|
|
77
|
+
payload: {},
|
|
78
|
+
toolName: "Bash",
|
|
79
|
+
toolInput: { command: "sudo apt install nodejs" },
|
|
80
|
+
params: { allowPatterns: [] },
|
|
81
|
+
};
|
|
82
|
+
expect(policy.fn(ctx)).toEqual(deny("sudo command blocked by failproofai"));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("allows non-sudo commands", () => {
|
|
86
|
+
const ctx = {
|
|
87
|
+
eventType: "PreToolUse" as const,
|
|
88
|
+
payload: {},
|
|
89
|
+
toolName: "Bash",
|
|
90
|
+
toolInput: { command: "ls -la" },
|
|
91
|
+
params: { allowPatterns: [] },
|
|
92
|
+
};
|
|
93
|
+
expect(policy.fn(ctx)).toEqual(allow());
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("allows patterns in allowPatterns", () => {
|
|
97
|
+
const ctx = {
|
|
98
|
+
eventType: "PreToolUse" as const,
|
|
99
|
+
payload: {},
|
|
100
|
+
toolName: "Bash",
|
|
101
|
+
toolInput: { command: "sudo systemctl status nginx" },
|
|
102
|
+
params: { allowPatterns: ["sudo systemctl status"] },
|
|
103
|
+
};
|
|
104
|
+
expect(policy.fn(ctx)).toEqual(allow());
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 端到端测试
|
|
112
|
+
|
|
113
|
+
端到端测试将真实的 `failproofai` 二进制文件作为子进程调用,通过 stdin 传入 JSON payload,并对 stdout 输出和退出码进行断言。这能够测试 Claude Code 所使用的完整集成路径。
|
|
114
|
+
|
|
115
|
+
### 环境配置
|
|
116
|
+
|
|
117
|
+
端到端测试直接从仓库源码运行二进制文件。在首次运行前,需要构建自定义 hook 文件从 `'failproofai'` 导入时所使用的 CJS 包:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
bun build src/index.ts --outdir dist --target node --format cjs
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
然后运行测试:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
bun run test:e2e
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
每当修改公共 hook API(`src/hooks/custom-hooks-registry.ts`、`src/hooks/policy-helpers.ts` 或 `src/hooks/policy-types.ts`)后,请重新构建 `dist/`。
|
|
130
|
+
|
|
131
|
+
### 端到端测试结构
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
__tests__/e2e/
|
|
135
|
+
helpers/
|
|
136
|
+
hook-runner.ts # 启动二进制进程,传入 payload JSON,捕获退出码 + stdout + stderr
|
|
137
|
+
fixture-env.ts # 为每个测试创建包含配置文件的隔离临时目录
|
|
138
|
+
payloads.ts # 符合 Claude 格式的各事件类型 payload 工厂函数
|
|
139
|
+
hooks/
|
|
140
|
+
builtin-policies.e2e.test.ts # 每个内置策略的真实子进程测试
|
|
141
|
+
custom-hooks.e2e.test.ts # 自定义 hook 的加载与评估
|
|
142
|
+
config-scopes.e2e.test.ts # 跨项目/本地/全局的配置合并
|
|
143
|
+
policy-params.e2e.test.ts # 各参数化策略的参数注入
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 使用端到端测试辅助工具
|
|
147
|
+
|
|
148
|
+
**`FixtureEnv`** - 每个测试的隔离环境:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { createFixtureEnv } from "../helpers/fixture-env";
|
|
152
|
+
|
|
153
|
+
const env = createFixtureEnv();
|
|
154
|
+
// env.cwd - 临时目录;作为 payload.cwd 传入以加载 .failproofai/policies-config.json
|
|
155
|
+
// env.home - 隔离的 home 目录;不会读取真实的 ~/.failproofai
|
|
156
|
+
|
|
157
|
+
env.writeConfig({
|
|
158
|
+
enabledPolicies: ["block-sudo"],
|
|
159
|
+
policyParams: {
|
|
160
|
+
"block-sudo": { allowPatterns: ["sudo systemctl status"] },
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`createFixtureEnv()` 会自动注册 `afterEach` 清理回调。
|
|
166
|
+
|
|
167
|
+
**`runHook`** - 调用二进制文件:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { runHook } from "../helpers/hook-runner";
|
|
171
|
+
import { Payloads } from "../helpers/payloads";
|
|
172
|
+
|
|
173
|
+
const result = await runHook(
|
|
174
|
+
"PreToolUse",
|
|
175
|
+
Payloads.preToolUse.bash("sudo apt install nodejs", env.cwd),
|
|
176
|
+
{ homeDir: env.home }
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect(result.exitCode).toBe(0);
|
|
180
|
+
expect(result.parsed?.hookSpecificOutput?.permissionDecision).toBe("deny");
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**`Payloads`** - 现成的 payload 工厂函数:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
Payloads.preToolUse.bash(command, cwd)
|
|
187
|
+
Payloads.preToolUse.write(filePath, content, cwd)
|
|
188
|
+
Payloads.preToolUse.read(filePath, cwd)
|
|
189
|
+
Payloads.postToolUse.bash(command, output, cwd)
|
|
190
|
+
Payloads.postToolUse.read(filePath, content, cwd)
|
|
191
|
+
Payloads.notification(message, cwd)
|
|
192
|
+
Payloads.stop(cwd)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 编写端到端测试
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { describe, it, expect } from "vitest";
|
|
199
|
+
import { createFixtureEnv } from "../helpers/fixture-env";
|
|
200
|
+
import { runHook } from "../helpers/hook-runner";
|
|
201
|
+
import { Payloads } from "../helpers/payloads";
|
|
202
|
+
|
|
203
|
+
describe("block-rm-rf (E2E)", () => {
|
|
204
|
+
it("denies rm -rf", async () => {
|
|
205
|
+
const env = createFixtureEnv();
|
|
206
|
+
env.writeConfig({ enabledPolicies: ["block-rm-rf"] });
|
|
207
|
+
|
|
208
|
+
const result = await runHook(
|
|
209
|
+
"PreToolUse",
|
|
210
|
+
Payloads.preToolUse.bash("rm -rf /", env.cwd),
|
|
211
|
+
{ homeDir: env.home }
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(result.exitCode).toBe(0);
|
|
215
|
+
expect(result.parsed?.hookSpecificOutput?.permissionDecision).toBe("deny");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("allows non-recursive rm", async () => {
|
|
219
|
+
const env = createFixtureEnv();
|
|
220
|
+
env.writeConfig({ enabledPolicies: ["block-rm-rf"] });
|
|
221
|
+
|
|
222
|
+
const result = await runHook(
|
|
223
|
+
"PreToolUse",
|
|
224
|
+
Payloads.preToolUse.bash("rm /tmp/file.txt", env.cwd),
|
|
225
|
+
{ homeDir: env.home }
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
expect(result.exitCode).toBe(0);
|
|
229
|
+
expect(result.stdout).toBe(""); // allow → 空 stdout
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 端到端响应格式
|
|
235
|
+
|
|
236
|
+
| 决策 | 退出码 | stdout |
|
|
237
|
+
|----------|-----------|--------|
|
|
238
|
+
| `PreToolUse` 拒绝 | `0` | `{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"..."}}` |
|
|
239
|
+
| `PostToolUse` 拒绝 | `0` | `{"hookSpecificOutput":{"additionalContext":"Blocked ... because: ..."}}` |
|
|
240
|
+
| 指令(非 Stop) | `0` | `{"hookSpecificOutput":{"additionalContext":"Instruction from failproofai: ..."}}` |
|
|
241
|
+
| Stop 指令 | `2` | stdout 为空;原因输出至 stderr |
|
|
242
|
+
| 允许 | `0` | 空字符串 |
|
|
243
|
+
|
|
244
|
+
### Vitest 配置
|
|
245
|
+
|
|
246
|
+
端到端测试使用 `vitest.config.e2e.mts`,配置如下:
|
|
247
|
+
|
|
248
|
+
- `environment: "node"` - 无需浏览器全局变量
|
|
249
|
+
- `pool: "forks"` - 真正的进程隔离(测试会派生子进程)
|
|
250
|
+
- `testTimeout: 20_000` - 每个测试 20 秒超时(含二进制启动和 hook 评估时间)
|
|
251
|
+
|
|
252
|
+
使用 `forks` 池至关重要:基于线程的 worker 共享 `globalThis`,可能干扰需要派生子进程的测试。基于进程的 forks 模式可避免此问题。
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 持续集成
|
|
257
|
+
|
|
258
|
+
在合并前,必须通过完整的 CI 运行(`bun run lint && bunx tsc --noEmit && bun run test:run && bun run build`)。端到端测试套件作为独立的 CI 任务并行运行。
|
|
259
|
+
|
|
260
|
+
完整的合并前检查清单请参阅 [Contributing](../CONTRIBUTING.md)。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.2-beta.
|
|
3
|
+
"version": "0.0.2-beta.9",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs"
|
|
@@ -32,7 +32,12 @@
|
|
|
32
32
|
"preuninstall": "node scripts/preuninstall.mjs",
|
|
33
33
|
"prepare": "bun run build",
|
|
34
34
|
"test:e2e": "vitest run --config vitest.config.e2e.mts",
|
|
35
|
-
"test:e2e:watch": "vitest --config vitest.config.e2e.mts"
|
|
35
|
+
"test:e2e:watch": "vitest --config vitest.config.e2e.mts",
|
|
36
|
+
"translate": "bun scripts/translate-docs/cli.ts",
|
|
37
|
+
"translate:readme": "bun scripts/translate-docs/cli.ts --readme-only",
|
|
38
|
+
"translate:docs": "bun scripts/translate-docs/cli.ts --docs-only",
|
|
39
|
+
"translate:dry-run": "bun scripts/translate-docs/cli.ts --dry-run",
|
|
40
|
+
"translate:validate": "bun scripts/translate-docs/cli.ts --validate"
|
|
36
41
|
},
|
|
37
42
|
"keywords": [
|
|
38
43
|
"claude",
|
|
@@ -85,6 +90,7 @@
|
|
|
85
90
|
"tailwind-merge": "^3.4.0",
|
|
86
91
|
"tailwindcss": "^4.1.18",
|
|
87
92
|
"typescript": "^6.0.2",
|
|
93
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
88
94
|
"vitest": "^4.0.18"
|
|
89
95
|
},
|
|
90
96
|
"dependencies": {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import type { CacheEntry, TranslationCache } from "./types";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const CACHE_FILE = join(__dirname, ".translation-cache.json");
|
|
9
|
+
|
|
10
|
+
export function contentHash(content: string): string {
|
|
11
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function readCache(): TranslationCache {
|
|
15
|
+
if (!existsSync(CACHE_FILE)) {
|
|
16
|
+
return { sourceHash: "", lastUpdated: "", translations: {} };
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
|
|
20
|
+
} catch {
|
|
21
|
+
return { sourceHash: "", lastUpdated: "", translations: {} };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function writeCache(cache: TranslationCache): void {
|
|
26
|
+
cache.lastUpdated = new Date().toISOString();
|
|
27
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getCacheKey(sourcePath: string, lang: string): string {
|
|
31
|
+
return `${sourcePath}::${lang}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isCached(
|
|
35
|
+
cache: TranslationCache,
|
|
36
|
+
sourcePath: string,
|
|
37
|
+
lang: string,
|
|
38
|
+
sourceContent: string,
|
|
39
|
+
): boolean {
|
|
40
|
+
const key = getCacheKey(sourcePath, lang);
|
|
41
|
+
const entry = cache.translations[key];
|
|
42
|
+
if (!entry) return false;
|
|
43
|
+
return entry.sourceHash === contentHash(sourceContent);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function setCacheEntry(
|
|
47
|
+
cache: TranslationCache,
|
|
48
|
+
sourcePath: string,
|
|
49
|
+
lang: string,
|
|
50
|
+
sourceContent: string,
|
|
51
|
+
inputTokens: number,
|
|
52
|
+
outputTokens: number,
|
|
53
|
+
): void {
|
|
54
|
+
const key = getCacheKey(sourcePath, lang);
|
|
55
|
+
cache.translations[key] = {
|
|
56
|
+
sourceHash: contentHash(sourceContent),
|
|
57
|
+
targetLang: lang,
|
|
58
|
+
translatedAt: new Date().toISOString(),
|
|
59
|
+
inputTokens,
|
|
60
|
+
outputTokens,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join, relative } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { LANGUAGES, getLanguagesByTier, getLanguageByCode, getModelForTier } from "./config";
|
|
7
|
+
import { getEnglishMdxPages, translateMdxPage } from "./mdx-translator";
|
|
8
|
+
import { translateReadme } from "./readme-translator";
|
|
9
|
+
import { updateDocsJson, readDocsConfig } from "./mintlify-nav";
|
|
10
|
+
import { readCache, writeCache, isCached, setCacheEntry } from "./cache";
|
|
11
|
+
import type { TranslationResult } from "./types";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const DOCS_DIR = join(__dirname, "..", "..", "docs");
|
|
15
|
+
|
|
16
|
+
const { values: args } = parseArgs({
|
|
17
|
+
options: {
|
|
18
|
+
languages: { type: "string", short: "l" },
|
|
19
|
+
tier: { type: "string", short: "t" },
|
|
20
|
+
pages: { type: "string", short: "p" },
|
|
21
|
+
"readme-only": { type: "boolean", default: false },
|
|
22
|
+
"docs-only": { type: "boolean", default: false },
|
|
23
|
+
"dry-run": { type: "boolean", default: false },
|
|
24
|
+
force: { type: "boolean", short: "f", default: false },
|
|
25
|
+
"update-nav": { type: "boolean", default: false },
|
|
26
|
+
validate: { type: "boolean", default: false },
|
|
27
|
+
model: { type: "string", short: "m" },
|
|
28
|
+
help: { type: "boolean", short: "h", default: false },
|
|
29
|
+
},
|
|
30
|
+
strict: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (args.help) {
|
|
34
|
+
console.log(`
|
|
35
|
+
Usage: bun scripts/translate-docs/cli.ts [options]
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
-l, --languages <codes> Comma-separated language codes (e.g. zh,ja,es)
|
|
39
|
+
-t, --tier <n> Translate all languages up to tier n (1, 2, or 3)
|
|
40
|
+
-p, --pages <files> Comma-separated page names to translate
|
|
41
|
+
--readme-only Only translate the README
|
|
42
|
+
--docs-only Only translate Mintlify docs
|
|
43
|
+
--dry-run Show what would be translated without calling the API
|
|
44
|
+
-f, --force Ignore cache, re-translate everything
|
|
45
|
+
--update-nav Regenerate docs.json navigation after translation
|
|
46
|
+
--validate Check all nav references resolve to files
|
|
47
|
+
-m, --model <model> Claude model override (default: Sonnet for Tier 1, Haiku for Tier 2/3)
|
|
48
|
+
-h, --help Show this help
|
|
49
|
+
|
|
50
|
+
Environment:
|
|
51
|
+
ANTHROPIC_API_KEY Required for translation (not needed for --dry-run or --validate)
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
bun scripts/translate-docs/cli.ts --tier 1 # Translate Tier 1 languages
|
|
55
|
+
bun scripts/translate-docs/cli.ts -l zh,ja --docs-only # Translate Chinese + Japanese docs only
|
|
56
|
+
bun scripts/translate-docs/cli.ts --dry-run --tier 3 # Preview all translations
|
|
57
|
+
bun scripts/translate-docs/cli.ts --validate # Check nav references
|
|
58
|
+
bun scripts/translate-docs/cli.ts --update-nav # Regenerate docs.json
|
|
59
|
+
`);
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveLanguages(): string[] {
|
|
64
|
+
if (args.languages) {
|
|
65
|
+
const codes = args.languages.split(",").map((c) => c.trim());
|
|
66
|
+
for (const code of codes) {
|
|
67
|
+
if (!getLanguageByCode(code)) {
|
|
68
|
+
console.error(`Unknown language code: ${code}`);
|
|
69
|
+
console.error(`Available: ${LANGUAGES.map((l) => l.code).join(", ")}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return codes;
|
|
74
|
+
}
|
|
75
|
+
const tier = args.tier ? parseInt(args.tier, 10) : 1;
|
|
76
|
+
if (tier < 1 || tier > 3) {
|
|
77
|
+
console.error("Tier must be 1, 2, or 3");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
return getLanguagesByTier(tier).map((l) => l.code);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function validateNavReferences(): Promise<boolean> {
|
|
84
|
+
const config = readDocsConfig();
|
|
85
|
+
const nav = config.navigation as Record<string, unknown>;
|
|
86
|
+
let valid = true;
|
|
87
|
+
let total = 0;
|
|
88
|
+
let missing = 0;
|
|
89
|
+
|
|
90
|
+
const languages = (nav.languages || []) as Array<{
|
|
91
|
+
language: string;
|
|
92
|
+
tabs: Array<{ groups: Array<{ pages: string[] }> }>;
|
|
93
|
+
}>;
|
|
94
|
+
|
|
95
|
+
if (languages.length === 0) {
|
|
96
|
+
// Flat tabs structure (not yet migrated to languages)
|
|
97
|
+
const tabs = (nav.tabs || []) as Array<{
|
|
98
|
+
groups: Array<{ pages: string[] }>;
|
|
99
|
+
}>;
|
|
100
|
+
for (const tab of tabs) {
|
|
101
|
+
for (const group of tab.groups) {
|
|
102
|
+
for (const page of group.pages) {
|
|
103
|
+
total++;
|
|
104
|
+
const filePath = join(DOCS_DIR, `${page}.mdx`);
|
|
105
|
+
if (!existsSync(filePath)) {
|
|
106
|
+
console.error(` MISSING: ${page} -> ${filePath}`);
|
|
107
|
+
missing++;
|
|
108
|
+
valid = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
for (const langEntry of languages) {
|
|
115
|
+
for (const tab of langEntry.tabs) {
|
|
116
|
+
for (const group of tab.groups) {
|
|
117
|
+
for (const page of group.pages) {
|
|
118
|
+
total++;
|
|
119
|
+
const filePath = join(DOCS_DIR, `${page}.mdx`);
|
|
120
|
+
if (!existsSync(filePath)) {
|
|
121
|
+
console.error(` MISSING [${langEntry.language}]: ${page} -> ${filePath}`);
|
|
122
|
+
missing++;
|
|
123
|
+
valid = false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (valid) {
|
|
132
|
+
console.log(`All ${total} page references are valid.`);
|
|
133
|
+
} else {
|
|
134
|
+
console.error(`\n${missing} of ${total} page references are missing.`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return valid;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function main() {
|
|
141
|
+
// Validate mode
|
|
142
|
+
if (args.validate) {
|
|
143
|
+
const valid = await validateNavReferences();
|
|
144
|
+
process.exit(valid ? 0 : 1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Update nav mode
|
|
148
|
+
if (args["update-nav"]) {
|
|
149
|
+
const langCodes = resolveLanguages();
|
|
150
|
+
console.log(`Updating docs.json with languages: ${langCodes.join(", ")}`);
|
|
151
|
+
updateDocsJson(langCodes);
|
|
152
|
+
console.log("docs.json updated.");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const langCodes = resolveLanguages();
|
|
157
|
+
const isDryRun = args["dry-run"];
|
|
158
|
+
const isForce = args.force;
|
|
159
|
+
const modelOverride = args.model;
|
|
160
|
+
|
|
161
|
+
/** Resolve the model for a language: CLI override wins, otherwise tier-based default. */
|
|
162
|
+
function resolveModel(lang: string): string {
|
|
163
|
+
if (modelOverride) return modelOverride;
|
|
164
|
+
const langConfig = getLanguageByCode(lang);
|
|
165
|
+
return getModelForTier(langConfig?.tier ?? 1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log(
|
|
169
|
+
`${isDryRun ? "[DRY RUN] " : ""}Translating into: ${langCodes.join(", ")}`,
|
|
170
|
+
);
|
|
171
|
+
if (!modelOverride) {
|
|
172
|
+
console.log(`Models: Tier 1 -> ${getModelForTier(1)}, Tier 2/3 -> ${getModelForTier(2)}`);
|
|
173
|
+
} else {
|
|
174
|
+
console.log(`Model override: ${modelOverride}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const results: TranslationResult[] = [];
|
|
178
|
+
const errors: Array<{ lang: string; source: string; error: string }> = [];
|
|
179
|
+
|
|
180
|
+
// Read cache once upfront — filter unchanged files before starting work
|
|
181
|
+
const cache = readCache();
|
|
182
|
+
|
|
183
|
+
// Concurrency limiter to avoid overwhelming the Anthropic API
|
|
184
|
+
const MAX_CONCURRENT = 10;
|
|
185
|
+
async function runWithConcurrency<T>(tasks: (() => Promise<T>)[]): Promise<T[]> {
|
|
186
|
+
const results: T[] = [];
|
|
187
|
+
let i = 0;
|
|
188
|
+
async function next(): Promise<void> {
|
|
189
|
+
while (i < tasks.length) {
|
|
190
|
+
const idx = i++;
|
|
191
|
+
results[idx] = await tasks[idx]();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
await Promise.all(Array.from({ length: Math.min(MAX_CONCURRENT, tasks.length) }, () => next()));
|
|
195
|
+
return results;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Translate docs
|
|
199
|
+
if (!args["readme-only"]) {
|
|
200
|
+
const pages = getEnglishMdxPages();
|
|
201
|
+
const filteredPages = args.pages
|
|
202
|
+
? pages.filter((p) => {
|
|
203
|
+
const rel = relative(DOCS_DIR, p).replace(".mdx", "");
|
|
204
|
+
return args.pages!.split(",").some((f) => rel.includes(f.trim()));
|
|
205
|
+
})
|
|
206
|
+
: pages;
|
|
207
|
+
|
|
208
|
+
// Read each page once and reuse across languages
|
|
209
|
+
const pageContents = new Map<string, string>();
|
|
210
|
+
for (const page of filteredPages) {
|
|
211
|
+
pageContents.set(page, readFileSync(page, "utf-8"));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Split into cached (skip) and uncached (need translation) upfront
|
|
215
|
+
type PageTask = { page: string; relPath: string; lang: string };
|
|
216
|
+
const cachedTasks: PageTask[] = [];
|
|
217
|
+
const uncachedTasks: PageTask[] = [];
|
|
218
|
+
|
|
219
|
+
for (const lang of langCodes) {
|
|
220
|
+
for (const page of filteredPages) {
|
|
221
|
+
const relPath = relative(DOCS_DIR, page);
|
|
222
|
+
const task = { page, relPath, lang };
|
|
223
|
+
if (!isForce && !isDryRun && isCached(cache, relPath, lang, pageContents.get(page)!)) {
|
|
224
|
+
cachedTasks.push(task);
|
|
225
|
+
} else {
|
|
226
|
+
uncachedTasks.push(task);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log(`\n${filteredPages.length} MDX pages x ${langCodes.length} languages = ${filteredPages.length * langCodes.length} total`);
|
|
232
|
+
console.log(` Cached (unchanged): ${cachedTasks.length}`);
|
|
233
|
+
console.log(` Need translation: ${uncachedTasks.length}`);
|
|
234
|
+
|
|
235
|
+
// Record cached results
|
|
236
|
+
for (const { page, relPath, lang } of cachedTasks) {
|
|
237
|
+
results.push({
|
|
238
|
+
lang,
|
|
239
|
+
sourcePath: page,
|
|
240
|
+
outputPath: join(DOCS_DIR, lang, relPath),
|
|
241
|
+
inputTokens: 0,
|
|
242
|
+
outputTokens: 0,
|
|
243
|
+
cached: true,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Translate uncached pages with concurrency limit
|
|
248
|
+
if (uncachedTasks.length > 0) {
|
|
249
|
+
const taskResults = await runWithConcurrency(
|
|
250
|
+
uncachedTasks.map(({ page, relPath, lang }) => async () => {
|
|
251
|
+
try {
|
|
252
|
+
const result = await translateMdxPage(page, lang, {
|
|
253
|
+
force: isForce,
|
|
254
|
+
dryRun: isDryRun,
|
|
255
|
+
model: resolveModel(lang),
|
|
256
|
+
cache,
|
|
257
|
+
});
|
|
258
|
+
const status = isDryRun
|
|
259
|
+
? "would translate"
|
|
260
|
+
: `translated (${result.inputTokens}+${result.outputTokens} tokens)`;
|
|
261
|
+
console.log(` ${relPath} [${lang}] -> ${status}`);
|
|
262
|
+
results.push(result);
|
|
263
|
+
if (!result.cached && !isDryRun) {
|
|
264
|
+
setCacheEntry(cache, relPath, lang, pageContents.get(page)!, result.inputTokens, result.outputTokens);
|
|
265
|
+
}
|
|
266
|
+
} catch (err) {
|
|
267
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
268
|
+
errors.push({ lang, source: relPath, error: msg });
|
|
269
|
+
console.error(` ${relPath} [${lang}] -> ERROR: ${msg}`);
|
|
270
|
+
}
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Translate README
|
|
277
|
+
if (!args["docs-only"]) {
|
|
278
|
+
console.log(`\nTranslating README...`);
|
|
279
|
+
|
|
280
|
+
// Read README once
|
|
281
|
+
const readmeSource = readFileSync(join(DOCS_DIR, "..", "README.md"), "utf-8");
|
|
282
|
+
const uncachedLangs: string[] = [];
|
|
283
|
+
for (const lang of langCodes) {
|
|
284
|
+
if (!isForce && !isDryRun && isCached(cache, "README.md", lang, readmeSource)) {
|
|
285
|
+
console.log(` README.${lang}.md -> cached`);
|
|
286
|
+
results.push({
|
|
287
|
+
lang,
|
|
288
|
+
sourcePath: join(DOCS_DIR, "..", "README.md"),
|
|
289
|
+
outputPath: join(DOCS_DIR, "i18n", `README.${lang}.md`),
|
|
290
|
+
inputTokens: 0,
|
|
291
|
+
outputTokens: 0,
|
|
292
|
+
cached: true,
|
|
293
|
+
});
|
|
294
|
+
} else {
|
|
295
|
+
uncachedLangs.push(lang);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (uncachedLangs.length > 0) {
|
|
300
|
+
await runWithConcurrency(
|
|
301
|
+
uncachedLangs.map((lang) => async () => {
|
|
302
|
+
try {
|
|
303
|
+
const langConfig = getLanguageByCode(lang)!;
|
|
304
|
+
const result = await translateReadme(lang, {
|
|
305
|
+
force: isForce,
|
|
306
|
+
dryRun: isDryRun,
|
|
307
|
+
model: resolveModel(lang),
|
|
308
|
+
cache,
|
|
309
|
+
});
|
|
310
|
+
const status = isDryRun
|
|
311
|
+
? "would translate"
|
|
312
|
+
: `translated (${result.inputTokens}+${result.outputTokens} tokens)`;
|
|
313
|
+
console.log(` README.${lang}.md -> ${langConfig.nativeName}: ${status}`);
|
|
314
|
+
results.push(result);
|
|
315
|
+
if (!result.cached && !isDryRun) {
|
|
316
|
+
setCacheEntry(cache, "README.md", lang, readmeSource, result.inputTokens, result.outputTokens);
|
|
317
|
+
}
|
|
318
|
+
} catch (err) {
|
|
319
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
320
|
+
errors.push({ lang, source: "README.md", error: msg });
|
|
321
|
+
console.error(` README.${lang}.md -> ERROR: ${msg}`);
|
|
322
|
+
}
|
|
323
|
+
}),
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Batch write cache once at the end
|
|
329
|
+
if (!isDryRun) {
|
|
330
|
+
writeCache(cache);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Summary
|
|
334
|
+
const translated = results.filter((r) => !r.cached && !isDryRun);
|
|
335
|
+
const cached = results.filter((r) => r.cached);
|
|
336
|
+
const totalInput = translated.reduce((s, r) => s + r.inputTokens, 0);
|
|
337
|
+
const totalOutput = translated.reduce((s, r) => s + r.outputTokens, 0);
|
|
338
|
+
|
|
339
|
+
console.log(`\n--- Summary ---`);
|
|
340
|
+
console.log(`Translated: ${translated.length}`);
|
|
341
|
+
console.log(`Cached (skipped): ${cached.length}`);
|
|
342
|
+
if (errors.length > 0) {
|
|
343
|
+
console.log(`Errors: ${errors.length}`);
|
|
344
|
+
}
|
|
345
|
+
if (totalInput > 0) {
|
|
346
|
+
console.log(`Total tokens: ${totalInput} input + ${totalOutput} output`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (errors.length > 0) {
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
main().catch((err) => {
|
|
355
|
+
console.error("Fatal error:", err);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
});
|