跳转到主要内容
自定义策略允许您为任何 Agent 行为编写规则:强制执行项目规范、防止偏离、限制破坏性操作、检测卡死的 Agent,或与 Slack、审批工作流等系统集成。它们使用与内置策略相同的 hook 事件系统以及 allowdenyinstruct 决策机制。

快速示例

// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";

customPolicies.add({
  name: "no-production-writes",
  description: "Block writes to paths containing 'production'",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (ctx.toolName !== "Write" && ctx.toolName !== "Edit") return allow();
    const path = ctx.toolInput?.file_path ?? "";
    if (path.includes("production")) {
      return deny("Writes to production paths are blocked");
    }
    return allow();
  },
});
安装:
failproofai policies --install --custom ./my-policies.js

两种加载自定义策略的方式

方式一:基于约定(推荐)

*policies.{js,mjs,ts} 文件放入 .failproofai/policies/ 目录,它们会被自动加载——无需任何标志或配置更改。这类似于 git hooks:放入文件即可生效。
# 项目级别——提交到 git,与团队共享
.failproofai/policies/security-policies.mjs
.failproofai/policies/workflow-policies.mjs

# 用户级别——个人配置,适用于所有项目
~/.failproofai/policies/my-policies.mjs
工作原理:
  • 项目目录和用户目录都会被扫描(取并集——不是先匹配作用域优先)
  • 每个目录内的文件按字母顺序加载。可使用 01-02- 等前缀控制顺序
  • 只加载匹配 *policies.{js,mjs,ts} 的文件,其他文件会被忽略
  • 每个文件独立加载(单文件失败开放)
  • 与显式 --custom 和内置策略兼容并存
基于约定的策略是在团队中共享策略的最简便方式。将 .failproofai/policies/ 提交到 git,每位团队成员都能自动获取这些策略。

方式二:显式文件路径

# 使用自定义策略文件安装
failproofai policies --install --custom ./my-policies.js

# 替换策略文件路径
failproofai policies --install --custom ./new-policies.js

# 从配置中移除自定义策略路径
failproofai policies --uninstall --custom
解析后的绝对路径以 customPoliciesPath 的形式存储在 policies-config.json 中。每次 hook 事件都会重新加载该文件——事件之间不存在缓存。

同时使用两种方式

基于约定的策略与显式 --custom 文件可以共存。加载顺序如下:
  1. 显式 customPoliciesPath 文件(如已配置)
  2. 项目约定文件({cwd}/.failproofai/policies/,按字母顺序)
  3. 用户约定文件(~/.failproofai/policies/,按字母顺序)

API

导入

import { customPolicies, allow, deny, instruct } from "failproofai";

customPolicies.add(hook)

注册一个策略。可多次调用以在同一文件中添加多个策略。
customPolicies.add({
  name: string;                         // 必填 - 唯一标识符
  description?: string;                 // 显示在 `failproofai policies` 输出中
  match?: { events?: HookEventType[] }; // 按事件类型过滤;省略则匹配所有事件
  fn: (ctx: PolicyContext) => PolicyResult | Promise<PolicyResult>;
});

决策辅助函数

函数效果适用场景
allow()静默允许操作操作安全,无需提示
deny(message)阻止操作Agent 不应执行此操作
instruct(message)添加上下文但不阻止为 Agent 提供额外上下文以保持正轨
deny(message) - 消息会以 "Blocked by failproofai:" 为前缀显示给 Claude。单个 deny 会短路所有后续评估。 instruct(message) - 消息会附加到 Claude 当前工具调用的上下文中。所有 instruct 消息会被累积并一并发送。
您可以通过在 policyParams 中添加 hint 字段,为任何 denyinstruct 消息追加额外指导——无需修改代码。这对自定义(custom/)、项目约定(.failproofai-project/)和用户约定(.failproofai-user/)策略同样适用。详情请参阅配置 → hint

信息性 allow 消息

allow(message) 允许操作向 Claude 发送一条信息性消息。该消息以 additionalContext 的形式通过 hook 处理器的 stdout 响应发送——与 instruct 使用相同的机制,但语义不同:它是状态更新,而非警告。
函数效果适用场景
allow(message)允许并向 Claude 发送上下文确认检查已通过,或说明为何跳过某项检查
使用场景:
  • 状态确认: allow("All CI checks passed.") — 告知 Claude 一切正常
  • 失败开放说明: allow("GitHub CLI not installed, skipping CI check.") — 告知 Claude 跳过检查的原因,使其拥有完整上下文
  • 多条消息累积: 若多个策略各自返回 allow(message),所有消息会以换行符连接并一并发送
customPolicies.add({
  name: "confirm-branch-status",
  match: { events: ["Stop"] },
  fn: async (ctx) => {
    const cwd = ctx.session?.cwd;
    if (!cwd) return allow("No working directory, skipping branch check.");

    // ... check branch status ...
    if (allPushed) {
      return allow("Branch is up to date with remote.");
    }
    return deny("Unpushed changes detected.");
  },
});

PolicyContext 字段

字段类型描述
eventTypestring"PreToolUse""PostToolUse""Notification""Stop"
toolNamestring | undefined被调用的工具(如 "Bash""Write""Read"
toolInputRecord<string, unknown> | undefined工具的输入参数
payloadRecord<string, unknown>来自 Claude Code 的完整原始事件载荷
sessionSessionMetadata | undefined会话上下文(见下文)

SessionMetadata 字段

字段类型描述
sessionIdstringClaude Code 会话标识符
cwdstringClaude Code 会话的工作目录
transcriptPathstring会话 JSONL 记录文件的路径

事件类型

事件触发时机toolInput 内容
PreToolUseClaude 执行工具之前工具的输入(如 Bash 对应 { command: "..." }
PostToolUse工具执行完成之后工具的输入 + tool_result(输出内容)
NotificationClaude 发送通知时{ message: "...", notification_type: "idle" | "permission_prompt" | ... } - hook 必须始终返回 allow(),不能阻止通知
StopClaude 会话结束时

评估顺序

策略按以下顺序评估:
  1. 内置策略(按定义顺序)
  2. 来自 customPoliciesPath 的显式自定义策略(按 .add() 顺序)
  3. 来自项目 .failproofai/policies/ 的约定策略(文件按字母顺序,文件内按 .add() 顺序)
  4. 来自用户 ~/.failproofai/policies/ 的约定策略(文件按字母顺序,文件内按 .add() 顺序)
第一个 deny 会短路所有后续策略。所有 instruct 消息会被累积并一并发送。

传递性导入

自定义策略文件可以使用相对路径导入本地模块:
// my-policies.js
import { isBlockedPath } from "./utils.js";
import { checkApproval } from "./approval-client.js";

customPolicies.add({
  name: "approval-gate",
  fn: async (ctx) => {
    if (ctx.toolName !== "Bash") return allow();
    const approved = await checkApproval(ctx.toolInput?.command, ctx.session?.sessionId);
    return approved ? allow() : deny("Approval required for this command");
  },
});
从入口文件可达的所有相对导入都会被解析。实现方式是将 from "failproofai" 的导入重写为实际的 dist 路径,并创建临时 .mjs 文件以确保 ESM 兼容性。

事件类型过滤

使用 match.events 限制策略的触发时机:
customPolicies.add({
  name: "require-summary-on-stop",
  match: { events: ["Stop"] },
  fn: async (ctx) => {
    // 仅在会话结束时触发
    // ctx.session.transcriptPath 包含完整的会话日志
    return allow();
  },
});
省略 match 则对每种事件类型都触发。

错误处理与失败模式

自定义策略采用失败开放原则:错误不会阻止内置策略运行或导致 hook 处理器崩溃。
失败情形行为
customPoliciesPath 未设置不运行显式自定义策略;约定策略和内置策略正常继续
文件未找到警告记录到 ~/.failproofai/hook.log;内置策略继续运行
语法/导入错误(显式)错误记录到 ~/.failproofai/hook.log;跳过显式自定义策略
语法/导入错误(约定)错误记录;跳过该文件,其他约定文件继续加载
fn 运行时抛出异常错误记录;该 hook 视为 allow;其他 hook 继续运行
fn 执行超过 10 秒超时记录;视为 allow
约定目录不存在不运行约定策略;不报错
如需调试自定义策略错误,可监控日志文件:
tail -f ~/.failproofai/hook.log

完整示例:多个策略

// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";

// 防止 Agent 写入 secrets/ 目录
customPolicies.add({
  name: "block-secrets-dir",
  description: "Prevent agent from writing to secrets/ directory",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (!["Write", "Edit"].includes(ctx.toolName ?? "")) return allow();
    const path = ctx.toolInput?.file_path ?? "";
    if (path.includes("secrets/")) return deny("Writing to secrets/ is not permitted");
    return allow();
  },
});

// 引导 Agent 保持正轨:提交前验证测试
customPolicies.add({
  name: "remind-test-before-commit",
  description: "Keep the agent on track: verify tests pass before committing",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (ctx.toolName !== "Bash") return allow();
    const cmd = ctx.toolInput?.command ?? "";
    if (/git\s+commit/.test(cmd)) {
      return instruct("Verify all tests pass before committing. Run `bun test` if you haven't already.");
    }
    return allow();
  },
});

// 在冻结期间防止计划外的依赖变更
customPolicies.add({
  name: "dependency-freeze",
  description: "Prevent unplanned dependency changes during freeze period",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (ctx.toolName !== "Bash") return allow();
    const cmd = ctx.toolInput?.command ?? "";
    const isInstall = /^(npm install|yarn add|bun add|pnpm add)\s+\S/.test(cmd);
    if (isInstall && process.env.DEPENDENCY_FREEZE === "1") {
      return deny("Package installs are frozen. Unset DEPENDENCY_FREEZE to allow.");
    }
    return allow();
  },
});

export { customPolicies };

示例文件

examples/ 目录包含可直接运行的策略文件:
文件内容
examples/policies-basic.js五个涵盖常见 Agent 失败模式的入门策略
examples/policies-advanced/index.js高级模式:传递性导入、异步调用、输出脱敏、会话结束 hook
examples/convention-policies/security-policies.mjs基于约定的安全策略(阻止 .env 写入、防止 git 历史重写)
examples/convention-policies/workflow-policies.mjs基于约定的工作流策略(测试提醒、审计文件写入)

使用显式文件示例

failproofai policies --install --custom ./examples/policies-basic.js

使用基于约定的示例

# 复制到项目级别
mkdir -p .failproofai/policies
cp examples/convention-policies/*.mjs .failproofai/policies/

# 或复制到用户级别
mkdir -p ~/.failproofai/policies
cp examples/convention-policies/*.mjs ~/.failproofai/policies/
无需安装命令——下次 hook 事件触发时,文件会被自动加载。