Перейти к основному содержанию
Пользовательские политики позволяют написать правила для любого поведения агента: применять соглашения проекта, предотвращать дрейф, блокировать деструктивные операции, обнаруживать зависшие агенты или интегрироваться с Slack, рабочими процессами одобрения и многим другим. Они используют ту же систему событий перехватчиков и решения allow, deny, instruct, что и встроенные политики.

Быстрый пример

// 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

Два способа загрузки пользовательских политик

Вариант 1: На основе соглашений (рекомендуется)

Поместите файлы *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}; остальные файлы игнорируются
  • Каждый файл загружается независимо (fail-open для каждого файла)
  • Работает вместе с явным --custom и встроенными политиками
Политики на основе соглашений — это самый простой способ поделиться политиками с командой. Закоммитьте .failproofai/policies/ в git, и каждый член команды получит их автоматически.

Вариант 2: Явный путь к файлу

# Установите с файлом пользовательских политик
failproofai policies --install --custom ./my-policies.js

# Замените путь к файлу политик
failproofai policies --install --custom ./new-policies.js

# Удалите путь к пользовательским политикам из конфигурации
failproofai policies --uninstall --custom
Разрешённый абсолютный путь сохраняется в policies-config.json как customPoliciesPath. Файл загружается заново при каждом событии перехватчика — кэширования между событиями нет.

Использование обоих способов вместе

Политики на основе соглашений и явный файл --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)Заблокировать операциюАгент не должен выполнять это действие
instruct(message)Добавить контекст без блокировкиДайте агенту дополнительный контекст для выполнения задачи
deny(message) — сообщение отображается Claude с префиксом "Blocked by failproofai:". Один deny короткозамыкает всю дальнейшую оценку. instruct(message) — сообщение добавляется в контекст Claude для текущего вызова инструмента. Все сообщения instruct накапливаются и доставляются вместе.
Вы можете добавить дополнительные рекомендации к любому сообщению deny или instruct, добавив поле hint в policyParams — без изменения кода. Это работает для пользовательских (custom/), проектных (failproofai-project/) и пользовательских (failproofai-user/) политик. См. Конфигурация → hint для подробностей.

Информационные сообщения allow

allow(message) разрешает операцию и отправляет информационное сообщение обратно Claude. Сообщение доставляется как additionalContext в ответе stdout обработчика перехватчика — тот же механизм, используемый instruct, но семантически отличается: это обновление статуса, а не предупреждение.
ФункцияЭффектИспользуйте когда
allow(message)Разрешить и отправить контекст ClaudeПодтвердить, что проверка прошла, или объяснить, почему проверка была пропущена
Варианты использования:
  • Подтверждения статуса: allow("All CI checks passed.") — указывает Claude, что всё зелено
  • Объяснения fail-open: 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

ПолеТипОписание
sessionIdstringИдентификатор сессии Claude Code
cwdstringРабочий каталог сессии Claude Code
transcriptPathstringПуть к файлу транскрипта JSONL сессии

Типы событий

СобытиеКогда срабатываетСодержимое toolInput
PreToolUseДо запуска инструмента ClaudeВход инструмента (например { command: "..." } для Bash)
PostToolUseПосле завершения инструментаВход инструмента + tool_result (выход)
NotificationКогда Claude отправляет уведомление{ message: "...", notification_type: "idle" | "permission_prompt" | ... } - перехватчики должны всегда возвращать allow(), они не могут блокировать уведомления
StopКогда сессия Claude заканчиваетсяПусто

Порядок оценки

Политики оцениваются в следующем порядке:
  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 полностью, чтобы срабатывать на каждом типе события.

Обработка ошибок и режимы отказа

Пользовательские политики — это fail-open: ошибки никогда не блокируют встроенные политики и не приводят к сбою обработчика перехватчика.
ОтказПоведение
customPoliciesPath не установленЯвные пользовательские политики не запускаются; встроенные политики продолжают нормально работать
Файл не найденПредупреждение записывается в ~/.failproofai/hook.log; встроенные политики продолжают работать
Синтаксис/ошибка импорта (явная)Ошибка записывается в ~/.failproofai/hook.log; явные пользовательские политики пропускаются
Синтаксис/ошибка импорта (соглашение)Ошибка записывается; этот файл пропускается, другие файлы соглашений загружаются
fn выбрасывает ошибку во время выполненияОшибка записывается; этот перехватчик рассматривается как allow; другие перехватчики продолжают работу
fn занимает больше 10 секундИстечение времени ожидания записывается; рассматривается как allow
Каталог соглашений отсутствуетПолитики соглашений не запускаются; нет ошибки
Для отладки ошибок пользовательских политик используйте файл журнала:
tail -f ~/.failproofai/hook.log

Полный пример: несколько политик

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

// Предотвращайте запись агента в каталог 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();
  },
});

// Держите агента на правильном пути: проверьте тесты перед коммитом
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Пять начальных политик, охватывающих типичные режимы отказа агента
examples/policies-advanced/index.jsПродвинутые паттерны: транзитивные импорты, асинхронные вызовы, очистка выводов и перехватчики завершения сессии
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/
Команда установки не требуется — файлы выбираются автоматически при следующем событии перехватчика.