failproofai 0.0.5-beta.0 → 0.0.6-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/.next/standalone/.failproofai/policies/workflow-policies.mjs +2 -1
  2. package/.next/standalone/.next/BUILD_ID +1 -1
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  22. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  23. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  26. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  27. package/.next/standalone/.next/server/app/index.html +1 -1
  28. package/.next/standalone/.next/server/app/index.rsc +15 -15
  29. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  31. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  32. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  33. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  34. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  35. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  37. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  38. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  41. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  44. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  48. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +1 -1
  51. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  52. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0r6vvp5._.js → [root-of-the-server]__0.~fd7s._.js} +2 -2
  53. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  54. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  55. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0z5dd-f._.js → [root-of-the-server]__0a.nuas._.js} +2 -2
  56. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  57. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  63. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  65. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  66. package/.next/standalone/.next/server/pages/404.html +2 -2
  67. package/.next/standalone/.next/server/pages/500.html +1 -1
  68. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  70. package/.next/standalone/.next/static/chunks/{0j_ivegn3i5wt.js → 0.z51twd.0l5z.js} +1 -1
  71. package/.next/standalone/.next/static/chunks/{04fpsjft~cje..js → 0hctoh28rg838.js} +1 -1
  72. package/.next/standalone/.next/static/chunks/{0.io32u7gjgsb.js → 0hplx-8c-4vpv.js} +1 -1
  73. package/.next/standalone/.next/static/chunks/{0yf_mmdukq6up.js → 0maq.q1t.ri85.js} +2 -2
  74. package/.next/standalone/.next/static/chunks/{0x8vagatq36_7.js → 0teq8wdh3po1n.js} +1 -1
  75. package/.next/standalone/.next/static/chunks/{04mtv9jnqknn3.js → 0uc0um_uz51m_.js} +1 -1
  76. package/.next/standalone/.next/static/chunks/{0h4qcn40dunn7.js → 0ul6fk-z.6k-0.js} +1 -1
  77. package/.next/standalone/.next/static/chunks/{0kvldut6ndzyz.js → 0w9lwqy0-v1dk.js} +1 -1
  78. package/.next/standalone/CHANGELOG.md +9 -1
  79. package/.next/standalone/README.md +2 -2
  80. package/.next/standalone/dist/cli.mjs +19 -2
  81. package/.next/standalone/docs/ar/architecture.mdx +66 -65
  82. package/.next/standalone/docs/ar/configuration.mdx +42 -42
  83. package/.next/standalone/docs/ar/custom-policies.mdx +63 -68
  84. package/.next/standalone/docs/architecture.mdx +2 -2
  85. package/.next/standalone/docs/configuration.mdx +1 -1
  86. package/.next/standalone/docs/custom-policies.mdx +2 -6
  87. package/.next/standalone/docs/de/architecture.mdx +92 -92
  88. package/.next/standalone/docs/de/configuration.mdx +35 -35
  89. package/.next/standalone/docs/de/custom-policies.mdx +50 -54
  90. package/.next/standalone/docs/es/architecture.mdx +73 -73
  91. package/.next/standalone/docs/es/configuration.mdx +25 -25
  92. package/.next/standalone/docs/es/custom-policies.mdx +49 -53
  93. package/.next/standalone/docs/fr/architecture.mdx +55 -55
  94. package/.next/standalone/docs/fr/configuration.mdx +26 -26
  95. package/.next/standalone/docs/fr/custom-policies.mdx +42 -46
  96. package/.next/standalone/docs/he/architecture.mdx +67 -67
  97. package/.next/standalone/docs/he/configuration.mdx +53 -52
  98. package/.next/standalone/docs/he/custom-policies.mdx +73 -77
  99. package/.next/standalone/docs/hi/architecture.mdx +107 -107
  100. package/.next/standalone/docs/hi/configuration.mdx +39 -39
  101. package/.next/standalone/docs/hi/custom-policies.mdx +77 -81
  102. package/.next/standalone/docs/i18n/README.ar.md +66 -66
  103. package/.next/standalone/docs/i18n/README.de.md +40 -40
  104. package/.next/standalone/docs/i18n/README.es.md +40 -40
  105. package/.next/standalone/docs/i18n/README.fr.md +44 -44
  106. package/.next/standalone/docs/i18n/README.he.md +67 -67
  107. package/.next/standalone/docs/i18n/README.hi.md +71 -71
  108. package/.next/standalone/docs/i18n/README.it.md +63 -63
  109. package/.next/standalone/docs/i18n/README.ja.md +55 -55
  110. package/.next/standalone/docs/i18n/README.ko.md +59 -59
  111. package/.next/standalone/docs/i18n/README.pt-br.md +45 -45
  112. package/.next/standalone/docs/i18n/README.ru.md +71 -71
  113. package/.next/standalone/docs/i18n/README.tr.md +76 -76
  114. package/.next/standalone/docs/i18n/README.vi.md +71 -71
  115. package/.next/standalone/docs/i18n/README.zh.md +53 -53
  116. package/.next/standalone/docs/it/architecture.mdx +55 -54
  117. package/.next/standalone/docs/it/configuration.mdx +45 -46
  118. package/.next/standalone/docs/it/custom-policies.mdx +77 -82
  119. package/.next/standalone/docs/ja/architecture.mdx +93 -93
  120. package/.next/standalone/docs/ja/configuration.mdx +48 -48
  121. package/.next/standalone/docs/ja/custom-policies.mdx +63 -67
  122. package/.next/standalone/docs/ko/architecture.mdx +66 -66
  123. package/.next/standalone/docs/ko/configuration.mdx +36 -36
  124. package/.next/standalone/docs/ko/custom-policies.mdx +73 -77
  125. package/.next/standalone/docs/pt-br/architecture.mdx +55 -55
  126. package/.next/standalone/docs/pt-br/configuration.mdx +36 -36
  127. package/.next/standalone/docs/pt-br/custom-policies.mdx +62 -66
  128. package/.next/standalone/docs/ru/architecture.mdx +59 -60
  129. package/.next/standalone/docs/ru/configuration.mdx +52 -53
  130. package/.next/standalone/docs/ru/custom-policies.mdx +70 -74
  131. package/.next/standalone/docs/tr/architecture.mdx +124 -124
  132. package/.next/standalone/docs/tr/configuration.mdx +46 -47
  133. package/.next/standalone/docs/tr/custom-policies.mdx +75 -78
  134. package/.next/standalone/docs/vi/architecture.mdx +65 -64
  135. package/.next/standalone/docs/vi/configuration.mdx +42 -42
  136. package/.next/standalone/docs/vi/custom-policies.mdx +68 -72
  137. package/.next/standalone/docs/zh/architecture.mdx +67 -67
  138. package/.next/standalone/docs/zh/configuration.mdx +35 -35
  139. package/.next/standalone/docs/zh/custom-policies.mdx +54 -58
  140. package/.next/standalone/package.json +1 -1
  141. package/.next/standalone/server.js +1 -1
  142. package/.next/standalone/src/hooks/builtin-policies.ts +30 -0
  143. package/README.md +2 -2
  144. package/dist/cli.mjs +19 -2
  145. package/package.json +1 -1
  146. package/src/hooks/builtin-policies.ts +30 -0
  147. /package/.next/standalone/.next/static/{ICeMHZ-8ZR7HY5GLm7oSJ → 8mygPGI5bzrtWK36ZYO59}/_buildManifest.js +0 -0
  148. /package/.next/standalone/.next/static/{ICeMHZ-8ZR7HY5GLm7oSJ → 8mygPGI5bzrtWK36ZYO59}/_clientMiddlewareManifest.js +0 -0
  149. /package/.next/standalone/.next/static/{ICeMHZ-8ZR7HY5GLm7oSJ → 8mygPGI5bzrtWK36ZYO59}/_ssgManifest.js +0 -0
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  title: 自定义策略
3
- description: "用 JavaScript 编写您自己的策略——强制执行项目规范、防止偏离、检测失败、与外部系统集成"
3
+ description: "用 JavaScript 编写自己的策略——强制执行项目规范、防止偏离、检测失败、与外部系统集成"
4
4
  icon: code
5
5
  ---
6
6
 
7
- 自定义策略允许您为任何 Agent 行为编写规则:强制执行项目规范、防止偏离、限制破坏性操作、检测卡死的 Agent,或与 Slack、审批工作流等系统集成。它们使用与内置策略相同的 hook 事件系统以及 `allow`、`deny`、`instruct` 决策机制。
7
+ 自定义策略允许你为任意 Agent 行为编写规则:强制执行项目规范、防止偏离、拦截破坏性操作、检测卡死的 Agent,或与 Slack、审批工作流等系统集成。它们使用与内置策略相同的 Hook 事件系统以及 `allow`、`deny`、`instruct` 决策机制。
8
8
 
9
9
  ---
10
10
 
@@ -39,34 +39,34 @@ failproofai policies --install --custom ./my-policies.js
39
39
 
40
40
  ## 两种加载自定义策略的方式
41
41
 
42
- ### 方式一:基于约定(推荐,v0.0.2-beta.7+)
42
+ ### 方式一:基于约定(推荐)
43
43
 
44
- 将 `*policies.{js,mjs,ts}` 文件放入 `.failproofai/policies/` 目录,它们会被自动加载——无需任何标志或配置更改。这类似于 git hooks:放入文件即可生效。
44
+ 将 `*policies.{js,mjs,ts}` 文件放入 `.failproofai/policies/` 目录,它们会被自动加载——无需任何命令行参数或配置变更。这与 git hooks 的工作方式类似:放入文件即可生效。
45
45
 
46
46
  ```
47
47
  # 项目级别——提交到 git,与团队共享
48
48
  .failproofai/policies/security-policies.mjs
49
49
  .failproofai/policies/workflow-policies.mjs
50
50
 
51
- # 用户级别——个人配置,适用于所有项目
51
+ # 用户级别——个人使用,适用于所有项目
52
52
  ~/.failproofai/policies/my-policies.mjs
53
53
  ```
54
54
 
55
55
  **工作原理:**
56
- - 项目目录和用户目录都会被扫描(取并集——不是先匹配作用域优先)
57
- - 每个目录内的文件按字母顺序加载。可使用 `01-`、`02-` 等前缀控制顺序
58
- - 只加载匹配 `*policies.{js,mjs,ts}` 的文件,其他文件会被忽略
59
- - 每个文件独立加载(单文件失败开放)
60
- - 与显式 `--custom` 和内置策略兼容并存
56
+ - 项目目录和用户目录都会被扫描(取并集,而非先命中即生效)
57
+ - 同一目录内的文件按字母顺序加载。可通过 `01-`、`02-` 等前缀控制顺序
58
+ - 只加载匹配 `*policies.{js,mjs,ts}` 的文件,其他文件被忽略
59
+ - 每个文件独立加载(单文件失败不影响其他文件)
60
+ - 可与显式 `--custom` 参数及内置策略共存
61
61
 
62
62
  <Tip>
63
- 基于约定的策略是在团队中共享策略的最简便方式。将 `.failproofai/policies/` 提交到 git,每位团队成员都能自动获取这些策略。
63
+ 基于约定的策略是在团队间共享策略的最简单方式。将 `.failproofai/policies/` 提交到 git,团队所有成员即可自动获得这些策略。
64
64
  </Tip>
65
65
 
66
- ### 方式二:显式文件路径
66
+ ### 方式二:显式指定文件路径
67
67
 
68
68
  ```bash
69
- # 使用自定义策略文件安装
69
+ # 安装时指定自定义策略文件
70
70
  failproofai policies --install --custom ./my-policies.js
71
71
 
72
72
  # 替换策略文件路径
@@ -76,13 +76,13 @@ failproofai policies --install --custom ./new-policies.js
76
76
  failproofai policies --uninstall --custom
77
77
  ```
78
78
 
79
- 解析后的绝对路径以 `customPoliciesPath` 的形式存储在 `policies-config.json` 中。每次 hook 事件都会重新加载该文件——事件之间不存在缓存。
79
+ 解析后的绝对路径以 `customPoliciesPath` 字段存储于 `policies-config.json` 中。每次 Hook 事件触发时都会重新加载该文件,事件之间不存在缓存。
80
80
 
81
- ### 同时使用两种方式
81
+ ### 两种方式同时使用
82
82
 
83
- 基于约定的策略与显式 `--custom` 文件可以共存。加载顺序如下:
83
+ 基于约定的策略和显式 `--custom` 文件可以共存。加载顺序如下:
84
84
 
85
- 1. 显式 `customPoliciesPath` 文件(如已配置)
85
+ 1. 显式 `customPoliciesPath` 文件(若已配置)
86
86
  2. 项目约定文件(`{cwd}/.failproofai/policies/`,按字母顺序)
87
87
  3. 用户约定文件(`~/.failproofai/policies/`,按字母顺序)
88
88
 
@@ -98,12 +98,12 @@ import { customPolicies, allow, deny, instruct } from "failproofai";
98
98
 
99
99
  ### `customPolicies.add(hook)`
100
100
 
101
- 注册一个策略。可多次调用以在同一文件中添加多个策略。
101
+ 注册一条策略。可多次调用,在同一文件中注册多条策略。
102
102
 
103
103
  ```ts
104
104
  customPolicies.add({
105
105
  name: string; // 必填 - 唯一标识符
106
- description?: string; // 显示在 `failproofai policies` 输出中
106
+ description?: string; // `failproofai policies` 输出中显示
107
107
  match?: { events?: HookEventType[] }; // 按事件类型过滤;省略则匹配所有事件
108
108
  fn: (ctx: PolicyContext) => PolicyResult | Promise<PolicyResult>;
109
109
  });
@@ -117,30 +117,26 @@ customPolicies.add({
117
117
  | `deny(message)` | 阻止操作 | Agent 不应执行此操作 |
118
118
  | `instruct(message)` | 添加上下文但不阻止 | 为 Agent 提供额外上下文以保持正轨 |
119
119
 
120
- `deny(message)` - 消息会以 `"Blocked by failproofai:"` 为前缀显示给 Claude。单个 `deny` 会短路所有后续评估。
120
+ `deny(message)` —— 消息会以 `"Blocked by failproofai:"` 为前缀显示给 Claude。单个 `deny` 会短路所有后续评估。
121
121
 
122
- `instruct(message)` - 消息会附加到 Claude 当前工具调用的上下文中。所有 `instruct` 消息会被累积并一并发送。
122
+ `instruct(message)` —— 消息会被追加到当前工具调用的 Claude 上下文中。所有 `instruct` 消息会被累积并一并传递。
123
123
 
124
124
  <Tip>
125
- 您可以通过在 `policyParams` 中添加 `hint` 字段,为任何 `deny` 或 `instruct` 消息追加额外指导——无需修改代码。这对自定义(`custom/`)、项目约定(`.failproofai-project/`)和用户约定(`.failproofai-user/`)策略同样适用。详情请参阅[配置 → hint](/zh/configuration#hint-cross-cutting)。
125
+ 你可以通过在 `policyParams` 中添加 `hint` 字段,为任意 `deny` 或 `instruct` 消息追加额外指导——无需修改代码。这对自定义策略(`custom/`)、项目约定策略(`.failproofai-project/`)和用户约定策略(`.failproofai-user/`)同样适用。详见 [配置 → hint](/zh/configuration#hint-cross-cutting)。
126
126
  </Tip>
127
127
 
128
- ### 信息性 allow 消息(Beta)
128
+ ### 信息性允许消息
129
129
 
130
- <Note>
131
- `allow(message)` 是自 v0.0.2-beta.3 起提供的 Beta 功能。API 可能在未来版本中发生变化。早期版本仅支持不带参数的 `allow()`。
132
- </Note>
133
-
134
- `allow(message)` 允许操作**并**向 Claude 发送一条信息性消息。该消息以 `additionalContext` 的形式通过 hook 处理器的 stdout 响应发送——与 `instruct` 使用相同的机制,但语义不同:它是状态更新,而非警告。
130
+ `allow(message)` 允许操作**并**向 Claude 发送一条信息性消息。该消息通过 Hook 处理器的 stdout 响应中的 `additionalContext` 字段传递——与 `instruct` 使用相同机制,但语义不同:它是状态更新,而非警告。
135
131
 
136
132
  | 函数 | 效果 | 适用场景 |
137
133
  |----------|--------|----------|
138
- | `allow(message)` | 允许并向 Claude 发送上下文 | 确认检查已通过,或说明为何跳过某项检查 |
134
+ | `allow(message)` | 允许并向 Claude 发送上下文 | 确认检查通过,或说明跳过检查的原因 |
139
135
 
140
136
  使用场景:
141
- - **状态确认:** `allow("All CI checks passed.")` 告知 Claude 一切正常
142
- - **失败开放说明:** `allow("GitHub CLI not installed, skipping CI check.")` 告知 Claude 跳过检查的原因,使其拥有完整上下文
143
- - **多条消息累积:** 若多个策略各自返回 `allow(message)`,所有消息会以换行符连接并一并发送
137
+ - **状态确认:** `allow("All CI checks passed.")` —— 告知 Claude 一切正常
138
+ - **失败开放说明:** `allow("GitHub CLI not installed, skipping CI check.")` —— 告知 Claude 跳过检查的原因,保持完整上下文
139
+ - **多条消息累积:** 若多条策略各自返回 `allow(message)`,所有消息会用换行符连接后一并传递
144
140
 
145
141
  ```js
146
142
  customPolicies.add({
@@ -164,9 +160,9 @@ customPolicies.add({
164
160
  | 字段 | 类型 | 描述 |
165
161
  |-------|------|-------------|
166
162
  | `eventType` | `string` | `"PreToolUse"`、`"PostToolUse"`、`"Notification"`、`"Stop"` |
167
- | `toolName` | `string \| undefined` | 被调用的工具(如 `"Bash"`、`"Write"`、`"Read"`) |
163
+ | `toolName` | `string \| undefined` | 被调用的工具名(如 `"Bash"`、`"Write"`、`"Read"`) |
168
164
  | `toolInput` | `Record<string, unknown> \| undefined` | 工具的输入参数 |
169
- | `payload` | `Record<string, unknown>` | 来自 Claude Code 的完整原始事件载荷 |
165
+ | `payload` | `Record<string, unknown>` | 来自 Claude Code 的完整原始事件负载 |
170
166
  | `session` | `SessionMetadata \| undefined` | 会话上下文(见下文) |
171
167
 
172
168
  ### `SessionMetadata` 字段
@@ -181,9 +177,9 @@ customPolicies.add({
181
177
 
182
178
  | 事件 | 触发时机 | `toolInput` 内容 |
183
179
  |-------|--------------|----------------------|
184
- | `PreToolUse` | Claude 执行工具之前 | 工具的输入(如 Bash 对应 `{ command: "..." }`) |
185
- | `PostToolUse` | 工具执行完成之后 | 工具的输入 + `tool_result`(输出内容) |
186
- | `Notification` | Claude 发送通知时 | `{ message: "...", notification_type: "idle" \| "permission_prompt" \| ... }` - hook 必须始终返回 `allow()`,不能阻止通知 |
180
+ | `PreToolUse` | Claude 运行工具之前 | 工具输入(如 Bash 对应 `{ command: "..." }`) |
181
+ | `PostToolUse` | 工具执行完成之后 | 工具输入 + `tool_result`(输出结果) |
182
+ | `Notification` | Claude 发送通知时 | `{ message: "...", notification_type: "idle" \| "permission_prompt" \| ... }` —— Hook 必须始终返回 `allow()`,不能阻止通知 |
187
183
  | `Stop` | Claude 会话结束时 | 空 |
188
184
 
189
185
  ---
@@ -193,19 +189,19 @@ customPolicies.add({
193
189
  策略按以下顺序评估:
194
190
 
195
191
  1. 内置策略(按定义顺序)
196
- 2. 来自 `customPoliciesPath` 的显式自定义策略(按 `.add()` 顺序)
197
- 3. 来自项目 `.failproofai/policies/` 的约定策略(文件按字母顺序,文件内按 `.add()` 顺序)
198
- 4. 来自用户 `~/.failproofai/policies/` 的约定策略(文件按字母顺序,文件内按 `.add()` 顺序)
192
+ 2. 来自 `customPoliciesPath` 的显式自定义策略(按 `.add()` 调用顺序)
193
+ 3. 项目约定策略,来自 `.failproofai/policies/`(文件按字母顺序,文件内按 `.add()` 顺序)
194
+ 4. 用户约定策略,来自 `~/.failproofai/policies/`(文件按字母顺序,文件内按 `.add()` 顺序)
199
195
 
200
196
  <Note>
201
- 第一个 `deny` 会短路所有后续策略。所有 `instruct` 消息会被累积并一并发送。
197
+ 第一个 `deny` 会短路所有后续策略。所有 `instruct` 消息会被累积并一并传递。
202
198
  </Note>
203
199
 
204
200
  ---
205
201
 
206
202
  ## 传递性导入
207
203
 
208
- 自定义策略文件可以使用相对路径导入本地模块:
204
+ 自定义策略文件可使用相对路径导入本地模块:
209
205
 
210
206
  ```js
211
207
  // my-policies.js
@@ -222,7 +218,7 @@ customPolicies.add({
222
218
  });
223
219
  ```
224
220
 
225
- 从入口文件可达的所有相对导入都会被解析。实现方式是将 `from "failproofai"` 的导入重写为实际的 dist 路径,并创建临时 `.mjs` 文件以确保 ESM 兼容性。
221
+ 所有从入口文件可达的相对导入都会被解析。其实现方式是将 `from "failproofai"` 的导入重写为实际的 dist 路径,并创建临时 `.mjs` 文件以确保 ESM 兼容性。
226
222
 
227
223
  ---
228
224
 
@@ -242,26 +238,26 @@ customPolicies.add({
242
238
  });
243
239
  ```
244
240
 
245
- 省略 `match` 则对每种事件类型都触发。
241
+ 省略 `match` 则对所有事件类型触发。
246
242
 
247
243
  ---
248
244
 
249
245
  ## 错误处理与失败模式
250
246
 
251
- 自定义策略采用**失败开放**原则:错误不会阻止内置策略运行或导致 hook 处理器崩溃。
247
+ 自定义策略采用**失败开放**机制:错误不会阻止内置策略运行,也不会导致 Hook 处理器崩溃。
252
248
 
253
- | 失败情形 | 行为 |
249
+ | 失败情况 | 行为 |
254
250
  |---------|----------|
255
251
  | `customPoliciesPath` 未设置 | 不运行显式自定义策略;约定策略和内置策略正常继续 |
256
- | 文件未找到 | 警告记录到 `~/.failproofai/hook.log`;内置策略继续运行 |
257
- | 语法/导入错误(显式) | 错误记录到 `~/.failproofai/hook.log`;跳过显式自定义策略 |
252
+ | 文件未找到 | 警告记录至 `~/.failproofai/hook.log`;内置策略继续运行 |
253
+ | 语法/导入错误(显式) | 错误记录至 `~/.failproofai/hook.log`;跳过显式自定义策略 |
258
254
  | 语法/导入错误(约定) | 错误记录;跳过该文件,其他约定文件继续加载 |
259
- | `fn` 运行时抛出异常 | 错误记录;该 hook 视为 `allow`;其他 hook 继续运行 |
255
+ | `fn` 运行时抛出异常 | 错误记录;该 Hook 视为 `allow`;其他 Hook 继续运行 |
260
256
  | `fn` 执行超过 10 秒 | 超时记录;视为 `allow` |
261
- | 约定目录不存在 | 不运行约定策略;不报错 |
257
+ | 约定目录不存在 | 不运行约定策略;无报错 |
262
258
 
263
259
  <Tip>
264
- 如需调试自定义策略错误,可监控日志文件:
260
+ 如需调试自定义策略错误,可监听日志文件:
265
261
 
266
262
  ```bash
267
263
  tail -f ~/.failproofai/hook.log
@@ -270,13 +266,13 @@ tail -f ~/.failproofai/hook.log
270
266
 
271
267
  ---
272
268
 
273
- ## 完整示例:多个策略
269
+ ## 完整示例:多条策略
274
270
 
275
271
  ```js
276
272
  // my-policies.js
277
273
  import { customPolicies, allow, deny, instruct } from "failproofai";
278
274
 
279
- // 防止 Agent 写入 secrets/ 目录
275
+ // 禁止 Agent secrets/ 目录写入
280
276
  customPolicies.add({
281
277
  name: "block-secrets-dir",
282
278
  description: "Prevent agent from writing to secrets/ directory",
@@ -289,7 +285,7 @@ customPolicies.add({
289
285
  },
290
286
  });
291
287
 
292
- // 引导 Agent 保持正轨:提交前验证测试
288
+ // 保持 Agent 在轨:提交前验证测试
293
289
  customPolicies.add({
294
290
  name: "remind-test-before-commit",
295
291
  description: "Keep the agent on track: verify tests pass before committing",
@@ -304,7 +300,7 @@ customPolicies.add({
304
300
  },
305
301
  });
306
302
 
307
- // 在冻结期间防止计划外的依赖变更
303
+ // 冻结期间禁止计划外的依赖变更
308
304
  customPolicies.add({
309
305
  name: "dependency-freeze",
310
306
  description: "Prevent unplanned dependency changes during freeze period",
@@ -331,8 +327,8 @@ export { customPolicies };
331
327
 
332
328
  | 文件 | 内容 |
333
329
  |------|----------|
334
- | `examples/policies-basic.js` | 五个涵盖常见 Agent 失败模式的入门策略 |
335
- | `examples/policies-advanced/index.js` | 高级模式:传递性导入、异步调用、输出脱敏、会话结束 hook |
330
+ | `examples/policies-basic.js` | 五条入门策略,涵盖常见 Agent 失败模式 |
331
+ | `examples/policies-advanced/index.js` | 高级模式:传递性导入、异步调用、输出清洗、会话结束 Hook |
336
332
  | `examples/convention-policies/security-policies.mjs` | 基于约定的安全策略(阻止 .env 写入、防止 git 历史重写) |
337
333
  | `examples/convention-policies/workflow-policies.mjs` | 基于约定的工作流策略(测试提醒、审计文件写入) |
338
334
 
@@ -354,4 +350,4 @@ mkdir -p ~/.failproofai/policies
354
350
  cp examples/convention-policies/*.mjs ~/.failproofai/policies/
355
351
  ```
356
352
 
357
- 无需安装命令——下次 hook 事件触发时,文件会被自动加载。
353
+ 无需执行安装命令——下次 Hook 事件触发时,文件会被自动加载。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "0.0.5-beta.0",
3
+ "version": "0.0.6-beta.0",
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"
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
9
9
  const hostname = process.env.HOSTNAME || '0.0.0.0'
10
10
 
11
11
  let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
12
- const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.5-beta.0"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"imgOptSkipMetadata":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
12
+ const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.6-beta.0"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"imgOptSkipMetadata":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
13
13
 
14
14
  process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
15
15
 
@@ -1064,6 +1064,36 @@ function requirePrBeforeStop(ctx: PolicyContext): PolicyResult {
1064
1064
  return allow(`PR #${pr.number} exists: ${pr.url}`);
1065
1065
  }
1066
1066
 
1067
+ // PR is merged/closed. The earlier origin/{baseBranch} checks may have
1068
+ // used a stale ref. Fetch and re-verify before denying.
1069
+ if (pr.state === "MERGED") {
1070
+ try {
1071
+ execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
1072
+ cwd,
1073
+ encoding: "utf8",
1074
+ timeout: 10000,
1075
+ });
1076
+ const freshAhead = execFileSync(
1077
+ "git",
1078
+ ["log", `origin/${baseBranch}..HEAD`, "--oneline"],
1079
+ { cwd, encoding: "utf8", timeout: 5000 },
1080
+ ).trim();
1081
+ if (!freshAhead) {
1082
+ return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
1083
+ }
1084
+ const freshDiff = execFileSync(
1085
+ "git",
1086
+ ["diff", "--stat", `origin/${baseBranch}`, "HEAD"],
1087
+ { cwd, encoding: "utf8", timeout: 5000 },
1088
+ ).trim();
1089
+ if (!freshDiff) {
1090
+ return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
1091
+ }
1092
+ } catch {
1093
+ // Fetch or git command failed — fall through to deny
1094
+ }
1095
+ }
1096
+
1067
1097
  return deny(
1068
1098
  `Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Run now: gh pr create`,
1069
1099
  );
package/README.md CHANGED
@@ -202,7 +202,7 @@ failproofai policies --install --custom ./my-policies.js
202
202
  | Function | Effect |
203
203
  |----------|--------|
204
204
  | `allow()` | Permit the operation |
205
- | `allow(message)` | Permit and send informational context to Claude *(beta)* |
205
+ | `allow(message)` | Permit and send informational context to Claude |
206
206
  | `deny(message)` | Block the operation; message shown to Claude |
207
207
  | `instruct(message)` | Add context to Claude's prompt; does not block |
208
208
 
@@ -220,7 +220,7 @@ failproofai policies --install --custom ./my-policies.js
220
220
 
221
221
  Custom hooks support transitive local imports, async/await, and access to `process.env`. Errors are fail-open (logged to `~/.failproofai/hook.log`, built-in policies continue). See [docs/custom-hooks.mdx](docs/custom-hooks.mdx) for the full guide.
222
222
 
223
- ### Convention-based policies (v0.0.2-beta.7+)
223
+ ### Convention-based policies
224
224
 
225
225
  Drop `*policies.{js,mjs,ts}` files into `.failproofai/policies/` and they're automatically loaded — no `--custom` flag or config changes needed. Works like git hooks: drop a file, it just works.
226
226
 
package/dist/cli.mjs CHANGED
@@ -959,6 +959,23 @@ function requirePrBeforeStop(ctx) {
959
959
  if (pr.state === "OPEN") {
960
960
  return allow(`PR #${pr.number} exists: ${pr.url}`);
961
961
  }
962
+ if (pr.state === "MERGED") {
963
+ try {
964
+ execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
965
+ cwd,
966
+ encoding: "utf8",
967
+ timeout: 1e4
968
+ });
969
+ const freshAhead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
970
+ if (!freshAhead) {
971
+ return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
972
+ }
973
+ const freshDiff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
974
+ if (!freshDiff) {
975
+ return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
976
+ }
977
+ } catch {}
978
+ }
962
979
  return deny(`Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Run now: gh pr create`);
963
980
  } catch {
964
981
  return allow("Could not check PR status, skipping.");
@@ -1951,7 +1968,7 @@ var init_hook_activity_store = __esm(() => {
1951
1968
  });
1952
1969
 
1953
1970
  // package.json
1954
- var version2 = "0.0.5-beta.0";
1971
+ var version2 = "0.0.6-beta.0";
1955
1972
  var init_package = () => {};
1956
1973
 
1957
1974
  // src/posthog-key.ts
@@ -3243,7 +3260,7 @@ import { realpathSync as realpathSync2 } from "fs";
3243
3260
  import { dirname as dirname5, resolve as resolve8 } from "path";
3244
3261
  import { fileURLToPath as fileURLToPath2 } from "url";
3245
3262
  // package.json
3246
- var version = "0.0.5-beta.0";
3263
+ var version = "0.0.6-beta.0";
3247
3264
 
3248
3265
  // bin/failproofai.mjs
3249
3266
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "0.0.5-beta.0",
3
+ "version": "0.0.6-beta.0",
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"
@@ -1064,6 +1064,36 @@ function requirePrBeforeStop(ctx: PolicyContext): PolicyResult {
1064
1064
  return allow(`PR #${pr.number} exists: ${pr.url}`);
1065
1065
  }
1066
1066
 
1067
+ // PR is merged/closed. The earlier origin/{baseBranch} checks may have
1068
+ // used a stale ref. Fetch and re-verify before denying.
1069
+ if (pr.state === "MERGED") {
1070
+ try {
1071
+ execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
1072
+ cwd,
1073
+ encoding: "utf8",
1074
+ timeout: 10000,
1075
+ });
1076
+ const freshAhead = execFileSync(
1077
+ "git",
1078
+ ["log", `origin/${baseBranch}..HEAD`, "--oneline"],
1079
+ { cwd, encoding: "utf8", timeout: 5000 },
1080
+ ).trim();
1081
+ if (!freshAhead) {
1082
+ return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
1083
+ }
1084
+ const freshDiff = execFileSync(
1085
+ "git",
1086
+ ["diff", "--stat", `origin/${baseBranch}`, "HEAD"],
1087
+ { cwd, encoding: "utf8", timeout: 5000 },
1088
+ ).trim();
1089
+ if (!freshDiff) {
1090
+ return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
1091
+ }
1092
+ } catch {
1093
+ // Fetch or git command failed — fall through to deny
1094
+ }
1095
+ }
1096
+
1067
1097
  return deny(
1068
1098
  `Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Run now: gh pr create`,
1069
1099
  );