Пользовательские политики позволяют вам написать правила для любого поведения агента: соблюдать соглашения проекта, предотвращать отклонения, ограничивать деструктивные операции, обнаруживать зависших агентов или интегрироваться с 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}; другие файлы игнорируются
- Каждый файл загружается независимо (открытое поведение при ошибке для каждого файла)
- Работает вместе с явным
--custom и встроенными политиками
Политики на основе соглашения — это самый простой способ создать стандарт качества для вашей организации. Зафиксируйте .failproofai/policies/ в git и каждый член команды автоматически получит одинаковые правила — никакой предварительной настройки для каждого разработчика не требуется. По мере того, как ваша команда обнаруживает новые сбои, добавляйте политику и выполняйте push. Со временем они станут живым стандартом качества, который постоянно улучшается с каждым вкладом.
Вариант 2: Явный путь к файлу
# Установка с пользовательским файлом политик
failproofai policies --install --custom ./my-policies.js
# Замена пути к файлу политик
failproofai policies --install --custom ./new-policies.js
# Удаление пути пользовательской политики из конфигурации
failproofai policies --uninstall --custom
Разрешённый абсолютный путь сохраняется в policies-config.json как customPoliciesPath. Файл загружается заново при каждом событии хука — кэширования между событиями нет.
Использование обоих способов вместе
Политики на основе соглашения и явный файл --custom могут сосуществовать. Порядок загрузки:
- Явный файл
customPoliciesPath (если настроен)
- Файлы соглашения проекта (
{cwd}/.failproofai/policies/, по алфавиту)
- Файлы соглашения пользователя (
~/.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/) политик тоже. See Configuration → hint для подробностей.
Информационные сообщения allow
allow(message) разрешает операцию и отправляет информационное сообщение обратно Claude. Сообщение доставляется как additionalContext в ответе 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.");
// ... проверка статуса ветки ...
if (allPushed) {
return allow("Branch is up to date with remote.");
}
return deny("Unpushed changes detected.");
},
});
Поля PolicyContext
| Поле | Тип | Описание |
|---|
eventType | string | "PreToolUse", "PostToolUse", "Notification", "Stop" |
toolName | string | undefined | Вызываемый инструмент (например, "Bash", "Write", "Read") |
toolInput | Record<string, unknown> | undefined | Входные параметры инструмента |
payload | Record<string, unknown> | Полная необработанная полезная нагрузка события из Claude Code |
session | SessionMetadata | undefined | Контекст сеанса (см. ниже) |
| Поле | Тип | Описание |
|---|
sessionId | string | Идентификатор сеанса Claude Code |
cwd | string | Рабочий каталог сеанса Claude Code |
transcriptPath | string | Путь к файлу стенограммы JSONL сеанса |
Типы событий
| Событие | Когда оно срабатывает | Содержимое toolInput |
|---|
PreToolUse | Перед запуском инструмента Claude | Входные данные инструмента (например, { command: "..." } для Bash) |
PostToolUse | После завершения инструмента | Входные данные инструмента + tool_result (выходные данные) |
Notification | Когда Claude отправляет уведомление | { message: "...", notification_type: "idle" | "permission_prompt" | ... } - хуки должны всегда возвращать allow(), они не могут блокировать уведомления |
Stop | Когда сеанс Claude заканчивается | Пусто |
Порядок оценки
Политики оцениваются в этом порядке:
- Встроенные политики (в порядке определения)
- Явные пользовательские политики из
customPoliciesPath (в порядке .add())
- Политики соглашения из проекта
.failproofai/policies/ (файлы по алфавиту, .add() порядок внутри)
- Политики соглашения из пользователя
~/.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 полностью, чтобы срабатывало на каждый тип события.
Обработка ошибок и режимы отказа
Пользовательские политики открыты при ошибке: ошибки никогда не блокируют встроенные политики и не приводят к сбою обработчика хука.
| Отказ | Поведение |
|---|
customPoliciesPath не установлен | Явные пользовательские политики не выполняются; политики соглашения и встроенные политики продолжают работать нормально |
| Файл не найден | Предупреждение записано в ~/.failproofai/hook.log; встроенные политики продолжают работать |
| Синтаксическая/ошибка импорта (явная) | Ошибка записана в ~/.failproofai/hook.log; явные пользовательские политики пропущены |
| Синтаксическая/ошибка импорта (соглашение) | Ошибка записана; этот файл пропущен, другие файлы соглашения всё ещё загружаются |
fn выбрасывает исключение во время выполнения | Ошибка записана; этот хук рассматривается как allow; другие хуки продолжают работать |
fn занимает дольше 10s | Timeout записан; рассматривается как 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/
Команда установки не требуется — файлы подбираются автоматически при следующем событии хука.