Le policy personalizzate ti permettono di scrivere regole per qualsiasi comportamento dell’agente: applicare convenzioni di progetto, prevenire deviazioni, bloccare operazioni distruttive, rilevare agenti bloccati, o integrarti con Slack, workflow di approvazione e altro ancora. Utilizzano lo stesso sistema di hook event e le stesse decisioni allow, deny, instruct delle policy integrate.
Esempio rapido
// 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();
},
});
Installalo:
failproofai policies --install --custom ./my-policies.js
Due modi per caricare policy personalizzate
Opzione 1: Basata su convenzione (consigliato)
Inserisci file *policies.{js,mjs,ts} in .failproofai/policies/ e verranno caricati automaticamente — nessun flag o cambio di configurazione necessario. Funziona come i git hooks: inserisci un file e basta.
# Livello di progetto — committato su git, condiviso con il team
.failproofai/policies/security-policies.mjs
.failproofai/policies/workflow-policies.mjs
# Livello utente — personale, si applica a tutti i progetti
~/.failproofai/policies/my-policies.mjs
Come funziona:
- Entrambe le directory di progetto e utente vengono scansionate (unione — non first-scope-wins)
- I file vengono caricati alfabeticamente all’interno di ogni directory. Utilizza il prefisso
01-, 02- per controllare l’ordine
- Vengono caricati solo i file che corrispondono a
*policies.{js,mjs,ts}; gli altri file vengono ignorati
- Ogni file viene caricato in modo indipendente (fail-open per file)
- Funziona insieme alle policy
--custom esplicite e alle policy integrate
Le policy di convenzione sono il modo più semplice per costruire uno standard di qualità per la tua organizzazione. Committi .failproofai/policies/ su git e ogni membro del team otterrà automaticamente le stesse regole — nessuna configurazione per sviluppatore necessaria. Mentre il tuo team scopre nuovi modi di fallimento, aggiungi una policy e fai il push. Nel tempo questi diventano uno standard di qualità dinamico che continua a migliorare con ogni contributo.
Opzione 2: Percorso file esplicito
# Installa con un file di policy personalizzate
failproofai policies --install --custom ./my-policies.js
# Sostituisci il percorso del file di policy
failproofai policies --install --custom ./new-policies.js
# Rimuovi il percorso delle policy personalizzate dalla configurazione
failproofai policies --uninstall --custom
Il percorso assoluto risolto viene memorizzato in policies-config.json come customPoliciesPath. Il file viene caricato fresh su ogni hook event - non c’è caching tra gli eventi.
Utilizzare entrambi insieme
Le policy di convenzione e il file --custom esplicito possono coesistere. Ordine di caricamento:
- File
customPoliciesPath esplicito (se configurato)
- File di convenzione di progetto (
{cwd}/.failproofai/policies/, alfabetico)
- File di convenzione utente (
~/.failproofai/policies/, alfabetico)
API
Import
import { customPolicies, allow, deny, instruct } from "failproofai";
customPolicies.add(hook)
Registra una policy. Chiama questa funzione tutte le volte necessarie per più policy nello stesso file.
customPolicies.add({
name: string; // required - unique identifier
description?: string; // shown in `failproofai policies` output
match?: { events?: HookEventType[] }; // filter by event type; omit to match all
fn: (ctx: PolicyContext) => PolicyResult | Promise<PolicyResult>;
});
Helper per decisioni
| Funzione | Effetto | Usare quando |
|---|
allow() | Permetti l’operazione silenziosamente | L’azione è sicura, nessun messaggio necessario |
deny(message) | Blocca l’operazione | L’agente non dovrebbe eseguire questa azione |
instruct(message) | Aggiungi contesto senza bloccare | Dai all’agente contesto aggiuntivo per rimanere in traccia |
deny(message) - il messaggio appare a Claude con prefisso "Blocked by failproofai:". Un singolo deny cortocircuita tutta la valutazione successiva.
instruct(message) - il messaggio viene aggiunto al contesto di Claude per la chiamata dello strumento corrente. Tutti i messaggi instruct vengono accumulati e consegnati insieme.
Puoi aggiungere guidance extra a qualsiasi messaggio deny o instruct aggiungendo un campo hint in policyParams — nessun cambio di codice necessario. Questo funziona anche per le policy personalizzate (custom/), di convenzione di progetto (.failproofai-project/), e di convenzione utente (.failproofai-user/). Vedi Configuration → hint per i dettagli.
allow(message) permette l’operazione e invia un messaggio informativo indietro a Claude. Il messaggio viene consegnato come additionalContext nella risposta stdout del gestore hook — lo stesso meccanismo utilizzato da instruct, ma semanticamente diverso: è un aggiornamento di stato, non un avvertimento.
| Funzione | Effetto | Usare quando |
|---|
allow(message) | Permetti e invia contesto a Claude | Conferma che un controllo è passato, o spiega perché un controllo è stato saltato |
Casi d’uso:
- Conferme di stato:
allow("All CI checks passed.") — dice a Claude che tutto è verde
- Spiegazioni fail-open:
allow("GitHub CLI not installed, skipping CI check.") — dice a Claude perché un controllo è stato saltato così ha il contesto completo
- Più messaggi si accumulano: se più policy restituiscono ciascuna
allow(message), tutti i messaggi vengono uniti con newline e consegnati insieme
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.");
},
});
Campi PolicyContext
| Campo | Tipo | Descrizione |
|---|
eventType | string | "PreToolUse", "PostToolUse", "Notification", "Stop" |
toolName | string | undefined | Lo strumento in fase di chiamata (es. "Bash", "Write", "Read") |
toolInput | Record<string, unknown> | undefined | I parametri di input dello strumento |
payload | Record<string, unknown> | Payload evento grezzo completo da Claude Code |
session | SessionMetadata | undefined | Contesto sessione (vedi sotto) |
| Campo | Tipo | Descrizione |
|---|
sessionId | string | Identificatore sessione Claude Code |
cwd | string | Directory di lavoro della sessione Claude Code |
transcriptPath | string | Percorso del file transcript JSONL della sessione |
Tipi di evento
| Evento | Quando si attiva | Contenuti toolInput |
|---|
PreToolUse | Prima che Claude esegua uno strumento | L’input dello strumento (es. { command: "..." } per Bash) |
PostToolUse | Dopo che uno strumento si completa | L’input dello strumento + tool_result (l’output) |
Notification | Quando Claude invia una notifica | { message: "...", notification_type: "idle" | "permission_prompt" | ... } - i hook devono sempre restituire allow(), non possono bloccare le notifiche |
Stop | Quando la sessione Claude termina | Vuoto |
Ordine di valutazione
Le policy vengono valutate in questo ordine:
- Policy integrate (in ordine di definizione)
- Policy personalizzate esplicite da
customPoliciesPath (in ordine .add())
- Policy di convenzione da
.failproofai/policies/ di progetto (file alfabetici, ordine .add() all’interno)
- Policy di convenzione da
~/.failproofai/policies/ utente (file alfabetici, ordine .add() all’interno)
Il primo deny cortocircuita tutte le policy successive. Tutti i messaggi instruct vengono accumulati e consegnati insieme.
Import transitivi
I file di policy personalizzate possono importare moduli locali utilizzando percorsi relativi:
// 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");
},
});
Tutti gli import relativi raggiungibili dal file di ingresso vengono risolti. Questo viene implementato riscrivendo gli import from "failproofai" al percorso dist effettivo e creando file .mjs temporanei per garantire la compatibilità ESM.
Filtraggio per tipo di evento
Usa match.events per limitare quando una policy si attiva:
customPolicies.add({
name: "require-summary-on-stop",
match: { events: ["Stop"] },
fn: async (ctx) => {
// Si attiva solo quando la sessione termina
// ctx.session.transcriptPath contiene il log di sessione completo
return allow();
},
});
Ometti match completamente per attivarsi su ogni tipo di evento.
Gestione degli errori e modalità di fallimento
Le policy personalizzate sono fail-open: gli errori non bloccano mai le policy integrate o fanno crashare il gestore hook.
| Errore | Comportamento |
|---|
customPoliciesPath non impostato | Nessuna policy personalizzata esplicita viene eseguita; le policy di convenzione e le policy integrate continuano normalmente |
| File non trovato | Avvertimento registrato in ~/.failproofai/hook.log; le policy integrate continuano |
| Errore di sintassi/import (esplicito) | Errore registrato in ~/.failproofai/hook.log; le policy personalizzate esplicite vengono saltate |
| Errore di sintassi/import (convenzione) | Errore registrato; quel file viene saltato, gli altri file di convenzione continuano comunque a caricarsi |
fn lancia un’eccezione a runtime | Errore registrato; questo hook viene trattato come allow; gli altri hook continuano |
fn impiega più di 10s | Timeout registrato; trattato come allow |
| Directory di convenzione mancante | Nessuna policy di convenzione viene eseguita; nessun errore |
Per eseguire il debug degli errori di policy personalizzate, osserva il file di log:tail -f ~/.failproofai/hook.log
Esempio completo: multiple policy
// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";
// Prevent agent from writing to secrets/ directory
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();
},
});
// Keep the agent on track: verify tests before committing
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();
},
});
// Prevent unplanned dependency changes during freeze
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 };
Esempi
La directory examples/ contiene file di policy pronti per l’uso:
| File | Contenuti |
|---|
examples/policies-basic.js | Cinque policy iniziali che coprono i comuni modi di fallimento dell’agente |
examples/policies-advanced/index.js | Pattern avanzati: import transitivi, chiamate asincrone, scrubbing dell’output, e hook di fine sessione |
examples/convention-policies/security-policies.mjs | Policy di convenzione sulla sicurezza (blocca scritture .env, previeni riscrittura della storia git) |
examples/convention-policies/workflow-policies.mjs | Policy di convenzione sul workflow (reminder di test, audit delle scritture di file) |
Utilizzare esempi di file espliciti
failproofai policies --install --custom ./examples/policies-basic.js
Utilizzare esempi basati su convenzione
# Copia a livello di progetto
mkdir -p .failproofai/policies
cp examples/convention-policies/*.mjs .failproofai/policies/
# O copia a livello di utente
mkdir -p ~/.failproofai/policies
cp examples/convention-policies/*.mjs ~/.failproofai/policies/
Nessun comando di installazione necessario — i file vengono racccolti automaticamente al prossimo hook event.