failproofai 0.0.10-beta.8 → 0.0.10
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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +7 -7
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/_not-found/page/next-font-manifest.json +6 -2
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -30
- package/.next/standalone/.next/server/app/_not-found.rsc +21 -26
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +21 -26
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +21 -21
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +21 -21
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -11
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +3 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/page/next-font-manifest.json +6 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/policies/page/next-font-manifest.json +6 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/project/[name]/page/next-font-manifest.json +6 -2
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/next-font-manifest.json +6 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +4 -4
- package/.next/standalone/.next/server/app/projects/page/next-font-manifest.json +6 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +3 -3
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0fjhqi9._.js → [root-of-the-server]__044xt9.._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_ob4n._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fe7_q_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vlhtkc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yfq1yr._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0370~qj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0mup1hi._.js → [root-of-the-server]__0609ezh._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__07_-mkc._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e9o9ri._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0og6yo7._.js → [root-of-the-server]__0l6swv1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0logebz._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mi5ejy._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0joez.y._.js → [root-of-the-server]__0podumr._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rkxer-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rl2kwi._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t5l7a5._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vg0uey._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ye1w50._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +32 -7
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10._f0s._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_03d7qyt._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0xb8ngh._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_0cdqd9w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_0sl~yqr._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_opencode-projects_ts_0op9gyp._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +7 -7
- package/.next/standalone/.next/server/next-font-manifest.js +1 -1
- package/.next/standalone/.next/server/next-font-manifest.json +21 -2
- package/.next/standalone/.next/server/pages/404.html +1 -30
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/{0k2yq8zevk9bl.js → 0j171xiqge4rv.js} +1 -1
- package/.next/standalone/.next/static/chunks/0kqar56yl~41o.js +6 -0
- package/.next/standalone/.next/static/chunks/{07i9r0t6n4cpy.js → 0lt8ko3lw.5yt.js} +1 -1
- package/.next/standalone/.next/static/chunks/0ml1.ck_5t36i.js +1 -0
- package/.next/standalone/.next/static/chunks/{0km4.rc8uvt_t.js → 0pkl..xgo-qox.js} +1 -1
- package/.next/standalone/.next/static/chunks/{12simlrcfk3g2.js → 0rnqmir4cd5p9.js} +2 -2
- package/.next/standalone/.next/static/chunks/{0bi2r.m~yokoo.js → 0w6fzf.07a24u.js} +1 -1
- package/.next/standalone/.next/static/chunks/0xbo5nl6w4lka.js +1 -0
- package/.next/standalone/.next/static/chunks/12l2t63hkyo2q.js +1 -0
- package/.next/standalone/.next/static/chunks/{0tyw4u3~2isbh.js → 12pt~2f.c1sha.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0by8zx1no6kt4.js → 14lii11wmo450.js} +1 -1
- package/.next/standalone/.next/static/chunks/179yytvmam0ug.js +1 -0
- package/.next/standalone/.next/static/chunks/17rm86uz2nd5a.css +2 -0
- package/.next/standalone/.next/static/chunks/{turbopack-0o7k.hakttp4k.js → turbopack-05z7a19q43zfq.js} +1 -1
- package/.next/standalone/.next/static/media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2 +0 -0
- package/.next/standalone/.next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2 +0 -0
- package/.next/standalone/.next/static/media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2 +0 -0
- package/.next/standalone/.opencode/plugins/failproofai.mjs +75 -15
- package/.next/standalone/app/actions/get-hooks-config.ts +25 -1
- package/.next/standalone/app/components/cli-badge.tsx +1 -1
- package/.next/standalone/app/globals.css +68 -111
- package/.next/standalone/app/layout.tsx +16 -56
- package/.next/standalone/app/policies/hooks-client.tsx +228 -44
- package/.next/standalone/components/navbar.tsx +16 -15
- package/.next/standalone/components/ui/button.tsx +4 -4
- package/.next/standalone/lib/gemini-projects.ts +64 -24
- package/.next/standalone/lib/opencode-projects.ts +9 -7
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/pi-extension/index.ts +113 -12
- package/.next/standalone/readme-arch-hq.gif +0 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +54 -241
- package/dist/cli.mjs +195 -75
- package/lib/gemini-projects.ts +64 -24
- package/lib/opencode-projects.ts +9 -7
- package/package.json +2 -2
- package/pi-extension/index.ts +113 -12
- package/scripts/launch.ts +6 -22
- package/scripts/parse-script-args.ts +1 -11
- package/scripts/translate-docs/config.ts +0 -1
- package/src/hooks/handler.ts +63 -6
- package/src/hooks/integrations.ts +31 -6
- package/src/hooks/policy-evaluator.ts +34 -2
- package/src/hooks/types.ts +52 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01hj~sd._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r6nu-._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04dywib._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06sb2gn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09jpajs._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0jm6jnh._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t2k4c5._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u1i~9~._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_02_tcps._.js +0 -32
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_11rg2a_._.js +0 -3
- package/.next/standalone/.next/static/chunks/0gq8kvc3blri~.js +0 -1
- package/.next/standalone/.next/static/chunks/0q5bmqop--9yk.js +0 -1
- package/.next/standalone/.next/static/chunks/0s41ggdsb2alw.js +0 -3
- package/.next/standalone/.next/static/chunks/0t_7i~pqwbcww.js +0 -6
- package/.next/standalone/.next/static/chunks/0xr8w5io1-kb9.css +0 -1
- package/.next/standalone/.next/static/chunks/164g0yuhpb2pi.js +0 -1
- package/.next/standalone/components/logo.tsx +0 -36
- package/.next/standalone/components/theme-toggle.tsx +0 -37
- package/.next/standalone/contexts/ThemeContext.tsx +0 -69
- package/.next/standalone/failproofai-hq.gif +0 -0
- package/.next/standalone/public/exospheresmall-dark.png +0 -0
- package/.next/standalone/public/exospheresmall.png +0 -0
- /package/.next/standalone/.next/static/{0PSH56_4bbPBaHiyPkthl → dAuQps6jUwCz9X1Q5FFOO}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{0PSH56_4bbPBaHiyPkthl → dAuQps6jUwCz9X1Q5FFOO}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{0PSH56_4bbPBaHiyPkthl → dAuQps6jUwCz9X1Q5FFOO}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.10
|
|
3
|
+
"version": "0.0.10",
|
|
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"
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
+
"@anthropic-ai/sdk": "^0.93.0",
|
|
74
75
|
"@tailwindcss/postcss": "^4.1.18",
|
|
75
76
|
"@tanstack/react-virtual": "^3.13.18",
|
|
76
77
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -91,7 +92,6 @@
|
|
|
91
92
|
"tailwind-merge": "^3.4.0",
|
|
92
93
|
"tailwindcss": "^4.1.18",
|
|
93
94
|
"typescript": "^6.0.2",
|
|
94
|
-
"@anthropic-ai/sdk": "^0.93.0",
|
|
95
95
|
"vitest": "^4.0.18"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
@@ -129,6 +129,36 @@ function canonicalizeToolName(piToolName: string | undefined): string | undefine
|
|
|
129
129
|
return PI_TOOL_MAP[piToolName] ?? piToolName;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Per-tool input-key translation. Pi's Read / Write / Edit tools deliver
|
|
134
|
+
* `path` (not `file_path`); failproofai's `block-env-files` and
|
|
135
|
+
* `block-secrets-write` builtins only read `file_path`, so without this map
|
|
136
|
+
* they silently no-op on Pi. `block-read-outside-cwd` already has a `path`
|
|
137
|
+
* fallback so it works either way. Pi's Edit tool nests `edits[{oldText,
|
|
138
|
+
* newText}]` which doesn't translate flatly to Claude's `{old_string,
|
|
139
|
+
* new_string}` — we only map the top-level `path`; the nested array stays
|
|
140
|
+
* Pi-shape (no current builtin reads it).
|
|
141
|
+
*
|
|
142
|
+
* Keep in sync with PI_TOOL_INPUT_MAP in src/hooks/types.ts.
|
|
143
|
+
*/
|
|
144
|
+
const PI_TOOL_INPUT_MAP: Record<string, Record<string, string>> = {
|
|
145
|
+
Read: { path: "file_path" },
|
|
146
|
+
Write: { path: "file_path" },
|
|
147
|
+
Edit: { path: "file_path" },
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
function canonicalizeToolInput(
|
|
151
|
+
canonicalToolName: string | undefined,
|
|
152
|
+
args: Record<string, unknown> | undefined,
|
|
153
|
+
): Record<string, unknown> | undefined {
|
|
154
|
+
if (!args || typeof args !== "object" || !canonicalToolName) return args;
|
|
155
|
+
const map = PI_TOOL_INPUT_MAP[canonicalToolName];
|
|
156
|
+
if (!map) return args;
|
|
157
|
+
const out: Record<string, unknown> = {};
|
|
158
|
+
for (const k of Object.keys(args)) out[map[k] ?? k] = args[k];
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
132
162
|
/** Resolve the cwd for the policy payload. Pi events don't include cwd, so
|
|
133
163
|
* fall back to the extension's process.cwd() — which is where Pi was
|
|
134
164
|
* launched and where `.failproofai/` config lives. */
|
|
@@ -201,6 +231,24 @@ function discoverPiSessionId(cwd: string): string | undefined {
|
|
|
201
231
|
* across multiple workspace roots) can't cross-attribute. Cleared on
|
|
202
232
|
* session_shutdown reasons `new`/`resume`/`fork` (Pi reuses the process). */
|
|
203
233
|
const cachedSessionIdByCwd = new Map<string, string>();
|
|
234
|
+
|
|
235
|
+
/** Pending Stop-policy deny reason from agent_end, keyed by sessionId.
|
|
236
|
+
* Drained by before_agent_start on the next user turn in the same Pi
|
|
237
|
+
* process. Cleared on every session_shutdown.
|
|
238
|
+
*
|
|
239
|
+
* Why this exists: Pi's agent_end has no Result type — the agent loop
|
|
240
|
+
* has already exited when it fires, so a deny return cannot keep Pi
|
|
241
|
+
* running the way Claude's exit-2-from-Stop does. The closest analog
|
|
242
|
+
* is to capture the deny here and re-inject it as a MANDATORY ACTION
|
|
243
|
+
* system-prompt addition on the NEXT before_agent_start, which fires
|
|
244
|
+
* after the user submits a prompt but before the agent loop runs.
|
|
245
|
+
* Best-effort: bounded by the Pi process lifetime — same bound Claude
|
|
246
|
+
* has on exit-2-from-Stop (kill the agent and the gate is missed).
|
|
247
|
+
*
|
|
248
|
+
* Why per-session not per-cwd: a Pi process can host multiple sessions
|
|
249
|
+
* via /resume and /fork; per-cwd would cross-attribute a stale block
|
|
250
|
+
* from a prior session into a fresh one. */
|
|
251
|
+
const pendingStopBlockBySession = new Map<string, string>();
|
|
204
252
|
function resolveSessionId(eventSessionId: string | undefined, cwd: string): string | undefined {
|
|
205
253
|
if (eventSessionId) {
|
|
206
254
|
cachedSessionIdByCwd.set(cwd, eventSessionId);
|
|
@@ -272,6 +320,17 @@ interface PiAgentEndEvent {
|
|
|
272
320
|
sessionId?: string;
|
|
273
321
|
}
|
|
274
322
|
|
|
323
|
+
/** Pi v0.73.x before_agent_start event payload. Fires once per turn,
|
|
324
|
+
* after the user submits a prompt but before the agent loop runs. */
|
|
325
|
+
interface PiBeforeAgentStartEvent {
|
|
326
|
+
type?: string;
|
|
327
|
+
prompt?: string;
|
|
328
|
+
/** The fully assembled system prompt for this turn — we append to it. */
|
|
329
|
+
systemPrompt?: string;
|
|
330
|
+
cwd?: string;
|
|
331
|
+
sessionId?: string;
|
|
332
|
+
}
|
|
333
|
+
|
|
275
334
|
interface PiExtensionApi {
|
|
276
335
|
on(event: string, handler: (event: unknown) => unknown): void;
|
|
277
336
|
}
|
|
@@ -280,9 +339,10 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
280
339
|
// tool_call → PreToolUse. Block tool execution when failproofai denies.
|
|
281
340
|
pi.on("tool_call", (event: unknown): unknown => {
|
|
282
341
|
const e = event as PiToolCallEvent;
|
|
342
|
+
const canonicalTool = canonicalizeToolName(e.toolName);
|
|
283
343
|
const decision = callPolicy("tool_call", {
|
|
284
|
-
tool_name:
|
|
285
|
-
tool_input: e.input,
|
|
344
|
+
tool_name: canonicalTool,
|
|
345
|
+
tool_input: canonicalizeToolInput(canonicalTool, e.input),
|
|
286
346
|
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
287
347
|
cwd: resolveCwd(e.cwd),
|
|
288
348
|
hook_event_name: "PreToolUse",
|
|
@@ -341,9 +401,10 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
341
401
|
// the activity store + stderr — but Pi keeps the original tool result.
|
|
342
402
|
pi.on("tool_result", (event: unknown): unknown => {
|
|
343
403
|
const e = event as PiToolResultEvent;
|
|
404
|
+
const canonicalTool = canonicalizeToolName(e.toolName);
|
|
344
405
|
callPolicy("tool_result", {
|
|
345
|
-
tool_name:
|
|
346
|
-
tool_input: e.input ?? {},
|
|
406
|
+
tool_name: canonicalTool,
|
|
407
|
+
tool_input: canonicalizeToolInput(canonicalTool, e.input) ?? {},
|
|
347
408
|
tool_response: { content: e.content, isError: e.isError },
|
|
348
409
|
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
349
410
|
cwd: resolveCwd(e.cwd),
|
|
@@ -352,21 +413,51 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
352
413
|
return undefined;
|
|
353
414
|
});
|
|
354
415
|
|
|
355
|
-
// agent_end → Stop.
|
|
356
|
-
// exited when this fires
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
//
|
|
416
|
+
// agent_end → Stop. Pi cannot veto agent_end (the agent loop has already
|
|
417
|
+
// exited when this fires — see the AgentEndEvent typedef in pi-coding-agent
|
|
418
|
+
// which has NO Result type). Instead we capture any deny reason and stash
|
|
419
|
+
// it keyed by sessionId for the next before_agent_start handler to drain.
|
|
420
|
+
// The 5 require-*-before-stop builtins thus enforce by gating the NEXT
|
|
421
|
+
// user turn's system prompt rather than by retrying the same loop. If the
|
|
422
|
+
// user kills Pi between turns, the gate is missed — same bound Claude has.
|
|
360
423
|
pi.on("agent_end", (event: unknown): unknown => {
|
|
361
424
|
const e = event as PiAgentEndEvent;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
425
|
+
const cwd = resolveCwd(e.cwd);
|
|
426
|
+
const sessionId = resolveSessionId(e.sessionId, cwd);
|
|
427
|
+
const decision = callPolicy("agent_end", {
|
|
428
|
+
session_id: sessionId,
|
|
429
|
+
cwd,
|
|
365
430
|
hook_event_name: "Stop",
|
|
366
431
|
});
|
|
432
|
+
if (decision.block && decision.reason && sessionId) {
|
|
433
|
+
pendingStopBlockBySession.set(sessionId, decision.reason);
|
|
434
|
+
debug(`agent_end deny stored for session=${sessionId}`);
|
|
435
|
+
}
|
|
367
436
|
return undefined;
|
|
368
437
|
});
|
|
369
438
|
|
|
439
|
+
// before_agent_start → drain any pending Stop-policy deny captured at
|
|
440
|
+
// agent_end. This is Pi's only first-class channel to influence the next
|
|
441
|
+
// turn before the LLM call: the result type accepts a `systemPrompt`
|
|
442
|
+
// replacement (chained across extensions) and an optional injected
|
|
443
|
+
// CustomMessage. We only return systemPrompt — sufficient for the LLM to
|
|
444
|
+
// see the MANDATORY ACTION directive immediately, and avoids polluting the
|
|
445
|
+
// visible conversation history with framework chrome. The reason text
|
|
446
|
+
// already carries the policy-attributed MANDATORY ACTION wording from
|
|
447
|
+
// policy-evaluator's Pi-Stop branch.
|
|
448
|
+
pi.on("before_agent_start", (event: unknown): unknown => {
|
|
449
|
+
const e = event as PiBeforeAgentStartEvent;
|
|
450
|
+
const cwd = resolveCwd(e.cwd);
|
|
451
|
+
const sessionId = resolveSessionId(e.sessionId, cwd);
|
|
452
|
+
if (!sessionId) return undefined;
|
|
453
|
+
const pending = pendingStopBlockBySession.get(sessionId);
|
|
454
|
+
if (!pending) return undefined;
|
|
455
|
+
pendingStopBlockBySession.delete(sessionId);
|
|
456
|
+
debug(`before_agent_start drains stop-block for session=${sessionId}`);
|
|
457
|
+
const base = e.systemPrompt ?? "";
|
|
458
|
+
return { systemPrompt: `${base}\n\n${pending}` };
|
|
459
|
+
});
|
|
460
|
+
|
|
370
461
|
// session_shutdown → SessionEnd. Observation-only; emits a SessionEnd
|
|
371
462
|
// record so per-session telemetry has a clean close. Reset the per-cwd
|
|
372
463
|
// sessionId cache for shutdown reasons that mean "Pi is starting a new
|
|
@@ -382,9 +473,19 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
382
473
|
reason: e.reason,
|
|
383
474
|
hook_event_name: "SessionEnd",
|
|
384
475
|
});
|
|
476
|
+
// Capture sessionId BEFORE the cache reset so we delete the pending
|
|
477
|
+
// entry under the just-ending session's id. After resetSessionIdCache,
|
|
478
|
+
// a subsequent resolveSessionId would re-discover from disk and could
|
|
479
|
+
// bind to a different (stale) file — wrong key for the cleanup below.
|
|
480
|
+
const sessionId = resolveSessionId(e.sessionId, cwd);
|
|
385
481
|
if (e.reason === "new" || e.reason === "resume" || e.reason === "fork") {
|
|
386
482
|
resetSessionIdCache(cwd);
|
|
387
483
|
}
|
|
484
|
+
// Drop any pending Stop-policy deny for this session on every shutdown
|
|
485
|
+
// reason — `quit` ends the session for good (don't leak the entry into
|
|
486
|
+
// GC); `new`/`resume`/`fork` start a different session in the same
|
|
487
|
+
// process and must not inherit the prior session's gate.
|
|
488
|
+
if (sessionId) pendingStopBlockBySession.delete(sessionId);
|
|
388
489
|
return undefined;
|
|
389
490
|
});
|
|
390
491
|
}
|
|
Binary file
|
|
@@ -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.10
|
|
12
|
+
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.10"},"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","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
|
|
13
13
|
|
|
14
14
|
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
|
|
15
15
|
|
package/README.md
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
______ _ __ ____ ___ ____
|
|
3
|
-
/ ____/___ _(_) /___ _________ ____ / __/ / | / _/
|
|
4
|
-
/ /_ / __ `/ / / __ \/ ___/ __ \/ __ \/ /_ / /| | / /
|
|
5
|
-
/ __/ / /_/ / / / /_/ / / / /_/ / /_/ / __/ / ___ |_/ /
|
|
6
|
-
/_/ \__,_/_/_/ .___/_/ \____/\____/_/ /_/ |_/___/
|
|
7
|
-
/_/
|
|
8
|
-
```
|
|
1
|
+
<div align="center">
|
|
9
2
|
|
|
10
|
-
|
|
3
|
+
<img src="https://d2wq11aau0arks.cloudfront.net/failproof/logo-wordmark.png" alt="failproof ai" width="220" />
|
|
11
4
|
|
|
12
|
-
[](https://befailproof.ai)
|
|
13
5
|
[](https://www.npmjs.com/package/failproofai)
|
|
14
|
-
[](LICENSE)
|
|
15
6
|
[](https://github.com/exospherehost/failproofai/actions)
|
|
16
7
|
[](https://join.slack.com/t/failproofai/shared_invite/zt-3v63b7k5e-O3NBHmj8X6n9gZSGDx6ggQ)
|
|
8
|
+
[](https://docs.befailproof.ai)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
|
|
11
|
+
**Translations:** [简体中文](./docs/i18n/README.zh.md) · [日本語](./docs/i18n/README.ja.md) · [한국어](./docs/i18n/README.ko.md) · [Español](./docs/i18n/README.es.md) · [Português](./docs/i18n/README.pt-br.md) · [Deutsch](./docs/i18n/README.de.md) · [Français](./docs/i18n/README.fr.md) · [Русский](./docs/i18n/README.ru.md) · [हिन्दी](./docs/i18n/README.hi.md) · [Türkçe](./docs/i18n/README.tr.md) · [Tiếng Việt](./docs/i18n/README.vi.md) · [Italiano](./docs/i18n/README.it.md) · [العربية](./docs/i18n/README.ar.md) · [עברית](./docs/i18n/README.he.md)
|
|
17
12
|
|
|
18
|
-
**
|
|
13
|
+
**Runtime failure resolution for coding agents.**
|
|
14
|
+
Hooks into Claude Code and Codex. Catches loops, dangerous actions, and secret leaks
|
|
15
|
+
before they become incidents. Zero latency. Runs locally.
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
</div>
|
|
21
18
|
|
|
22
19
|
<p align="center">
|
|
23
|
-
<img src="
|
|
20
|
+
<img src="readme-arch-hq.gif" alt="Failproof AI in action" width="800" />
|
|
24
21
|
</p>
|
|
25
22
|
|
|
23
|
+
---
|
|
24
|
+
|
|
26
25
|
## Supported agent CLIs
|
|
27
26
|
|
|
28
27
|
<p align="center">
|
|
@@ -76,283 +75,97 @@ The easiest way to manage policies that keep your AI agents reliable, on-task, a
|
|
|
76
75
|
|
|
77
76
|
> Install hooks for one or any combination: `failproofai policies --install --cli opencode pi gemini` (or `--cli claude codex copilot cursor opencode pi gemini`). Omit `--cli` to auto-detect installed CLIs and prompt. **GitHub Copilot CLI, Cursor Agent, OpenCode, Pi, and Gemini CLI support are in beta — testing is ongoing.**
|
|
78
77
|
|
|
79
|
-
- **39 Built-in Policies** - Catch common agent failure modes out of the box. Block destructive commands, prevent secret leakage, keep agents inside project boundaries, detect loops, and more.
|
|
80
|
-
- **Custom Policies** - Write your own reliability rules in JavaScript. Use the `allow`/`deny`/`instruct` API to enforce conventions, prevent drift, gate operations, or integrate with external systems.
|
|
81
|
-
- **Easy Configuration** - Tune any policy without writing code. Set allowlists, protected branches, thresholds per-project or globally. Three-scope config merges automatically.
|
|
82
|
-
- **Agent Monitor** - See what your agents did while you were away. Browse sessions, inspect every tool call, and review exactly where policies fired.
|
|
83
|
-
|
|
84
|
-
Everything runs locally - no data leaves your machine.
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## Requirements
|
|
89
|
-
|
|
90
|
-
- Node.js >= 20.9.0
|
|
91
|
-
- Bun >= 1.3.0 (optional - only needed for development / building from source)
|
|
92
|
-
|
|
93
78
|
---
|
|
94
79
|
|
|
95
80
|
## Install
|
|
96
81
|
|
|
97
|
-
```
|
|
82
|
+
```sh
|
|
98
83
|
npm install -g failproofai
|
|
99
|
-
# or
|
|
100
|
-
bun add -g failproofai
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Quick start
|
|
106
|
-
|
|
107
|
-
### 1. Enable policies globally
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
84
|
failproofai policies --install
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Writes hook entries into `~/.claude/settings.json`. Claude Code will now invoke failproofai before and after each tool call.
|
|
114
|
-
|
|
115
|
-
### 2. Launch the dashboard
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
85
|
failproofai
|
|
119
86
|
```
|
|
120
87
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
### 3. Check what's active
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
failproofai policies
|
|
127
|
-
```
|
|
88
|
+
30 built-in policies activate immediately. Dashboard at `localhost:8020`.
|
|
128
89
|
|
|
129
90
|
---
|
|
130
91
|
|
|
131
|
-
##
|
|
132
|
-
|
|
133
|
-
### Scopes
|
|
92
|
+
## What it stops
|
|
134
93
|
|
|
135
|
-
|
|
|
136
|
-
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
94
|
+
| Policy | What it blocks |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `block-push-master` | Direct pushes to `main` / `master` |
|
|
97
|
+
| `block-force-push` | `git push --force` |
|
|
98
|
+
| `block-work-on-main` | Commits, merges, rebases on `main` / `master` |
|
|
99
|
+
| `block-rm-rf` | Recursive file deletion |
|
|
100
|
+
| `sanitize-api-keys` | API keys leaking into agent context |
|
|
140
101
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
failproofai policies --install block-sudo block-rm-rf sanitize-api-keys
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Remove policies
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
failproofai policies --uninstall
|
|
151
|
-
# or for a specific scope:
|
|
152
|
-
failproofai policies --uninstall --scope project
|
|
153
|
-
```
|
|
102
|
+
→ [All 30 built-in policies](https://docs.befailproof.ai/built-in-policies)
|
|
154
103
|
|
|
155
104
|
---
|
|
156
105
|
|
|
157
|
-
##
|
|
158
|
-
|
|
159
|
-
Policy configuration lives in `~/.failproofai/policies-config.json` (global) or `.failproofai/policies-config.json` in your project (per-project).
|
|
160
|
-
|
|
161
|
-
```json
|
|
162
|
-
{
|
|
163
|
-
"enabledPolicies": [
|
|
164
|
-
"block-sudo",
|
|
165
|
-
"block-rm-rf",
|
|
166
|
-
"sanitize-api-keys",
|
|
167
|
-
"block-push-master",
|
|
168
|
-
"block-env-files",
|
|
169
|
-
"block-read-outside-cwd"
|
|
170
|
-
],
|
|
171
|
-
"policyParams": {
|
|
172
|
-
"block-sudo": {
|
|
173
|
-
"allowPatterns": ["sudo systemctl status", "sudo journalctl"],
|
|
174
|
-
"hint": "Use apt-get directly without sudo."
|
|
175
|
-
},
|
|
176
|
-
"block-push-master": {
|
|
177
|
-
"protectedBranches": ["main", "release", "prod"],
|
|
178
|
-
"hint": "Try creating a fresh branch instead."
|
|
179
|
-
},
|
|
180
|
-
"sanitize-api-keys": {
|
|
181
|
-
"additionalPatterns": [
|
|
182
|
-
{ "regex": "myco_[A-Za-z0-9]{32}", "label": "MyCo API key" }
|
|
183
|
-
]
|
|
184
|
-
},
|
|
185
|
-
"warn-large-file-write": {
|
|
186
|
-
"thresholdKb": 512
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
**Three config scopes** are merged automatically (project → local → global). See [docs/configuration.mdx](docs/configuration.mdx) for full merge rules.
|
|
106
|
+
## Your own policies
|
|
193
107
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
## Built-in policies
|
|
197
|
-
|
|
198
|
-
| Policy | Description | Configurable |
|
|
199
|
-
|--------|-------------|:---:|
|
|
200
|
-
| `block-sudo` | Prevent agents from running privileged system commands | `allowPatterns` |
|
|
201
|
-
| `block-rm-rf` | Prevent accidental recursive file deletion | `allowPaths` |
|
|
202
|
-
| `block-curl-pipe-sh` | Prevent agents from piping untrusted scripts to shell | |
|
|
203
|
-
| `block-failproofai-commands` | Prevent self-uninstallation | |
|
|
204
|
-
| `sanitize-jwt` | Stop JWT tokens from leaking into agent context | |
|
|
205
|
-
| `sanitize-api-keys` | Stop API keys from leaking into agent context | `additionalPatterns` |
|
|
206
|
-
| `sanitize-connection-strings` | Stop database credentials from leaking into agent context | |
|
|
207
|
-
| `sanitize-private-key-content` | Redact PEM private key blocks from output | |
|
|
208
|
-
| `sanitize-bearer-tokens` | Redact Authorization Bearer tokens from output | |
|
|
209
|
-
| `block-env-files` | Keep agents from reading .env files | |
|
|
210
|
-
| `protect-env-vars` | Prevent agents from printing environment variables | |
|
|
211
|
-
| `block-read-outside-cwd` | Keep agents inside project boundaries | `allowPaths` |
|
|
212
|
-
| `block-secrets-write` | Prevent writes to private key and certificate files | `additionalPatterns` |
|
|
213
|
-
| `block-push-master` | Prevent accidental pushes to main/master | `protectedBranches` |
|
|
214
|
-
| `block-work-on-main` | Keep agents off protected branches | `protectedBranches` |
|
|
215
|
-
| `block-force-push` | Prevent `git push --force` | |
|
|
216
|
-
| `warn-git-amend` | Remind agents before amending commits | |
|
|
217
|
-
| `warn-git-stash-drop` | Remind agents before dropping stashes | |
|
|
218
|
-
| `warn-all-files-staged` | Catch accidental `git add -A` | |
|
|
219
|
-
| `warn-destructive-sql` | Catch DROP/DELETE SQL before execution | |
|
|
220
|
-
| `warn-schema-alteration` | Catch ALTER TABLE before execution | |
|
|
221
|
-
| `warn-large-file-write` | Catch unexpectedly large file writes | `thresholdKb` |
|
|
222
|
-
| `warn-package-publish` | Catch accidental `npm publish` | |
|
|
223
|
-
| `warn-background-process` | Catch unintended background process launches | |
|
|
224
|
-
| `warn-global-package-install` | Catch unintended global package installs | |
|
|
225
|
-
| …and more | | |
|
|
226
|
-
|
|
227
|
-
Full policy details and parameter reference: [docs/built-in-policies.mdx](docs/built-in-policies.mdx)
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## Custom policies
|
|
232
|
-
|
|
233
|
-
Write your own policies to keep agents reliable and on-task:
|
|
108
|
+
Drop a file into `.failproofai/policies/` — it loads automatically, no flags needed.
|
|
109
|
+
Commit it and the whole team gets it on next pull.
|
|
234
110
|
|
|
235
111
|
```js
|
|
236
|
-
import { customPolicies,
|
|
112
|
+
import { customPolicies, deny, allow } from "failproofai";
|
|
237
113
|
|
|
238
114
|
customPolicies.add({
|
|
239
115
|
name: "no-production-writes",
|
|
240
|
-
description: "Block writes to paths containing 'production'",
|
|
241
116
|
match: { events: ["PreToolUse"] },
|
|
242
117
|
fn: async (ctx) => {
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
if (path.includes("production")) return deny("Writes to production paths are blocked");
|
|
118
|
+
if (ctx.toolInput?.file_path?.includes("production"))
|
|
119
|
+
return deny("Writes to production paths are blocked.");
|
|
246
120
|
return allow();
|
|
247
121
|
},
|
|
248
122
|
});
|
|
249
123
|
```
|
|
250
124
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
```bash
|
|
254
|
-
failproofai policies --install --custom ./my-policies.js
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### Decision helpers
|
|
125
|
+
Three decisions available to every policy:
|
|
258
126
|
|
|
259
|
-
|
|
|
260
|
-
|
|
127
|
+
| Decision | Effect |
|
|
128
|
+
|---|---|
|
|
261
129
|
| `allow()` | Permit the operation |
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
264
|
-
| `instruct(message)` | Add context to Claude's prompt; does not block |
|
|
265
|
-
|
|
266
|
-
### Context object (`ctx`)
|
|
267
|
-
|
|
268
|
-
| Field | Type | Description |
|
|
269
|
-
|-------|------|-------------|
|
|
270
|
-
| `eventType` | `string` | `"PreToolUse"`, `"PostToolUse"`, `"Notification"`, `"Stop"` |
|
|
271
|
-
| `toolName` | `string` | Tool being called (`"Bash"`, `"Write"`, `"Read"`, …) |
|
|
272
|
-
| `toolInput` | `object` | Tool's input parameters |
|
|
273
|
-
| `payload` | `object` | Full raw event payload |
|
|
274
|
-
| `session.cwd` | `string` | Working directory of the Claude Code session |
|
|
275
|
-
| `session.sessionId` | `string` | Session identifier |
|
|
276
|
-
| `session.transcriptPath` | `string` | Path to the session transcript file |
|
|
277
|
-
|
|
278
|
-
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.
|
|
279
|
-
|
|
280
|
-
### Convention-based policies
|
|
281
|
-
|
|
282
|
-
Drop `*policies.{js,mjs,ts}` files into `.failproofai/policies/` and they're automatically loaded — no flags or config changes needed. Commit the directory to git and every team member gets the same quality standards automatically.
|
|
283
|
-
|
|
284
|
-
```text
|
|
285
|
-
# Project level — committed to git, shared with the team
|
|
286
|
-
.failproofai/policies/security-policies.mjs
|
|
287
|
-
.failproofai/policies/workflow-policies.mjs
|
|
288
|
-
|
|
289
|
-
# User level — personal, applies to all projects
|
|
290
|
-
~/.failproofai/policies/my-policies.mjs
|
|
291
|
-
```
|
|
130
|
+
| `deny(message)` | Block it — message goes back to the agent |
|
|
131
|
+
| `instruct(message)` | Let it through, but add context to the agent's next prompt |
|
|
292
132
|
|
|
293
|
-
|
|
133
|
+
→ [Custom policies guide](https://docs.befailproof.ai/custom-policies)
|
|
294
134
|
|
|
295
135
|
---
|
|
296
136
|
|
|
297
|
-
##
|
|
137
|
+
## Session visibility
|
|
298
138
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
```bash
|
|
304
|
-
FAILPROOFAI_TELEMETRY_DISABLED=1 failproofai
|
|
305
|
-
```
|
|
139
|
+
Every tool call your agent makes is logged locally. The dashboard shows what ran,
|
|
140
|
+
what was blocked, and what the policy told the agent — so you're not guessing
|
|
141
|
+
when something goes wrong. → [Dashboard guide](https://docs.befailproof.ai/dashboard)
|
|
306
142
|
|
|
307
143
|
---
|
|
308
144
|
|
|
309
145
|
## Documentation
|
|
310
146
|
|
|
311
|
-
|
|
|
312
|
-
|
|
313
|
-
| [Getting Started](docs/getting-started
|
|
314
|
-
| [Built-in Policies](docs/built-in-policies
|
|
315
|
-
| [Custom Policies](docs/custom-policies
|
|
316
|
-
| [Configuration](docs/configuration
|
|
317
|
-
| [Dashboard](docs/dashboard
|
|
318
|
-
| [Architecture](docs/architecture
|
|
319
|
-
| [Testing](docs/testing.mdx) | Running tests and writing new ones |
|
|
320
|
-
|
|
321
|
-
### Run docs locally
|
|
322
|
-
|
|
323
|
-
```bash
|
|
324
|
-
docker build -f Dockerfile.docs -t failproofai-docs .
|
|
325
|
-
docker run --rm -p 3000:3000 failproofai-docs
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
Opens the Mintlify docs site at `http://localhost:3000`. The container watches for changes if you mount the docs directory:
|
|
329
|
-
|
|
330
|
-
```bash
|
|
331
|
-
docker run --rm -p 3000:3000 -v $(pwd)/docs:/app/docs failproofai-docs
|
|
332
|
-
```
|
|
147
|
+
| | |
|
|
148
|
+
|---|---|
|
|
149
|
+
| [Getting Started](https://docs.befailproof.ai/getting-started) | Installation and first steps |
|
|
150
|
+
| [Built-in Policies](https://docs.befailproof.ai/built-in-policies) | All 30 policies with parameters |
|
|
151
|
+
| [Custom Policies](https://docs.befailproof.ai/custom-policies) | Write your own |
|
|
152
|
+
| [Configuration](https://docs.befailproof.ai/configuration) | Config scopes and merge rules |
|
|
153
|
+
| [Dashboard](https://docs.befailproof.ai/dashboard) | Session monitor and policy activity |
|
|
154
|
+
| [Architecture](https://docs.befailproof.ai/architecture) | How the hook system works |
|
|
333
155
|
|
|
334
156
|
---
|
|
335
157
|
|
|
336
|
-
##
|
|
337
|
-
|
|
338
|
-
This repo's `.claude/settings.json` uses `bun ./bin/failproofai.mjs --hook <EventType>` instead of the standard `npx -y failproofai` command. This is because running `npx -y failproofai` inside the failproofai project itself creates a self-referencing conflict.
|
|
339
|
-
|
|
340
|
-
For all other repos, the recommended approach is `npx -y failproofai`, installed via:
|
|
341
|
-
|
|
342
|
-
```bash
|
|
343
|
-
failproofai policies --install --scope project
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
## Contributing
|
|
158
|
+
## License
|
|
347
159
|
|
|
348
|
-
|
|
160
|
+
MIT with [Commons Clause](https://commonsclause.com/) — free for internal and personal use; commercial resale of failproofai itself requires a separate agreement. See [LICENSE](./LICENSE) for the full text.
|
|
349
161
|
|
|
350
162
|
---
|
|
351
163
|
|
|
352
|
-
##
|
|
164
|
+
## Contributing
|
|
353
165
|
|
|
354
|
-
See [
|
|
166
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md). New policies, edge cases, and translations all welcome.
|
|
355
167
|
|
|
356
168
|
---
|
|
357
169
|
|
|
358
|
-
Built
|
|
170
|
+
Built by [Nivedit Jain](https://github.com/NiveditJain) and [Nikita Agarwal](https://github.com/nk-ag).
|
|
171
|
+
[befailproof.ai](https://befailproof.ai)
|