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.
Files changed (174) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +7 -7
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +4 -4
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +4 -4
  17. package/.next/standalone/.next/server/app/_not-found/page/next-font-manifest.json +6 -2
  18. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  20. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.html +1 -30
  23. package/.next/standalone/.next/server/app/_not-found.rsc +21 -26
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +21 -26
  25. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  26. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -11
  27. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  29. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +21 -21
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +21 -21
  36. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  37. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -11
  38. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +3 -2
  39. package/.next/standalone/.next/server/app/page/build-manifest.json +4 -4
  40. package/.next/standalone/.next/server/app/page/next-font-manifest.json +6 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +2 -2
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +4 -4
  46. package/.next/standalone/.next/server/app/policies/page/next-font-manifest.json +6 -2
  47. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  48. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  49. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +4 -4
  52. package/.next/standalone/.next/server/app/project/[name]/page/next-font-manifest.json +6 -2
  53. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
  55. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +4 -4
  58. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/next-font-manifest.json +6 -2
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +3 -3
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +4 -4
  65. package/.next/standalone/.next/server/app/projects/page/next-font-manifest.json +6 -2
  66. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  67. package/.next/standalone/.next/server/app/projects/page.js +3 -3
  68. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0fjhqi9._.js → [root-of-the-server]__044xt9.._.js} +2 -2
  71. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_ob4n._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fe7_q_._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vlhtkc._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yfq1yr._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0370~qj._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0mup1hi._.js → [root-of-the-server]__0609ezh._.js} +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__07_-mkc._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e9o9ri._.js +4 -0
  80. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0og6yo7._.js → [root-of-the-server]__0l6swv1._.js} +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0logebz._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mi5ejy._.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0joez.y._.js → [root-of-the-server]__0podumr._.js} +3 -3
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rkxer-._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rl2kwi._.js +4 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t5l7a5._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vg0uey._.js +4 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ye1w50._.js +4 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +32 -7
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10._f0s._.js +4 -0
  91. package/.next/standalone/.next/server/chunks/ssr/_03d7qyt._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/_0xb8ngh._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/app_0cdqd9w._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +2 -2
  96. package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_0sl~yqr._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/lib_opencode-projects_ts_0op9gyp._.js +1 -1
  98. package/.next/standalone/.next/server/middleware-build-manifest.js +7 -7
  99. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  100. package/.next/standalone/.next/server/next-font-manifest.json +21 -2
  101. package/.next/standalone/.next/server/pages/404.html +1 -30
  102. package/.next/standalone/.next/server/pages/500.html +1 -1
  103. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  104. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  105. package/.next/standalone/.next/static/chunks/{0k2yq8zevk9bl.js → 0j171xiqge4rv.js} +1 -1
  106. package/.next/standalone/.next/static/chunks/0kqar56yl~41o.js +6 -0
  107. package/.next/standalone/.next/static/chunks/{07i9r0t6n4cpy.js → 0lt8ko3lw.5yt.js} +1 -1
  108. package/.next/standalone/.next/static/chunks/0ml1.ck_5t36i.js +1 -0
  109. package/.next/standalone/.next/static/chunks/{0km4.rc8uvt_t.js → 0pkl..xgo-qox.js} +1 -1
  110. package/.next/standalone/.next/static/chunks/{12simlrcfk3g2.js → 0rnqmir4cd5p9.js} +2 -2
  111. package/.next/standalone/.next/static/chunks/{0bi2r.m~yokoo.js → 0w6fzf.07a24u.js} +1 -1
  112. package/.next/standalone/.next/static/chunks/0xbo5nl6w4lka.js +1 -0
  113. package/.next/standalone/.next/static/chunks/12l2t63hkyo2q.js +1 -0
  114. package/.next/standalone/.next/static/chunks/{0tyw4u3~2isbh.js → 12pt~2f.c1sha.js} +1 -1
  115. package/.next/standalone/.next/static/chunks/{0by8zx1no6kt4.js → 14lii11wmo450.js} +1 -1
  116. package/.next/standalone/.next/static/chunks/179yytvmam0ug.js +1 -0
  117. package/.next/standalone/.next/static/chunks/17rm86uz2nd5a.css +2 -0
  118. package/.next/standalone/.next/static/chunks/{turbopack-0o7k.hakttp4k.js → turbopack-05z7a19q43zfq.js} +1 -1
  119. package/.next/standalone/.next/static/media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2 +0 -0
  120. package/.next/standalone/.next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2 +0 -0
  121. package/.next/standalone/.next/static/media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2 +0 -0
  122. package/.next/standalone/.opencode/plugins/failproofai.mjs +75 -15
  123. package/.next/standalone/app/actions/get-hooks-config.ts +25 -1
  124. package/.next/standalone/app/components/cli-badge.tsx +1 -1
  125. package/.next/standalone/app/globals.css +68 -111
  126. package/.next/standalone/app/layout.tsx +16 -56
  127. package/.next/standalone/app/policies/hooks-client.tsx +228 -44
  128. package/.next/standalone/components/navbar.tsx +16 -15
  129. package/.next/standalone/components/ui/button.tsx +4 -4
  130. package/.next/standalone/lib/gemini-projects.ts +64 -24
  131. package/.next/standalone/lib/opencode-projects.ts +9 -7
  132. package/.next/standalone/package.json +2 -2
  133. package/.next/standalone/pi-extension/index.ts +113 -12
  134. package/.next/standalone/readme-arch-hq.gif +0 -0
  135. package/.next/standalone/server.js +1 -1
  136. package/README.md +54 -241
  137. package/dist/cli.mjs +195 -75
  138. package/lib/gemini-projects.ts +64 -24
  139. package/lib/opencode-projects.ts +9 -7
  140. package/package.json +2 -2
  141. package/pi-extension/index.ts +113 -12
  142. package/scripts/launch.ts +6 -22
  143. package/scripts/parse-script-args.ts +1 -11
  144. package/scripts/translate-docs/config.ts +0 -1
  145. package/src/hooks/handler.ts +63 -6
  146. package/src/hooks/integrations.ts +31 -6
  147. package/src/hooks/policy-evaluator.ts +34 -2
  148. package/src/hooks/types.ts +52 -0
  149. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01hj~sd._.js +0 -4
  150. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r6nu-._.js +0 -3
  151. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04dywib._.js +0 -4
  152. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06sb2gn._.js +0 -4
  153. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09jpajs._.js +0 -4
  154. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0jm6jnh._.js +0 -3
  155. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t2k4c5._.js +0 -3
  156. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u1i~9~._.js +0 -4
  157. package/.next/standalone/.next/server/chunks/ssr/_02_tcps._.js +0 -32
  158. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +0 -5
  159. package/.next/standalone/.next/server/chunks/ssr/_11rg2a_._.js +0 -3
  160. package/.next/standalone/.next/static/chunks/0gq8kvc3blri~.js +0 -1
  161. package/.next/standalone/.next/static/chunks/0q5bmqop--9yk.js +0 -1
  162. package/.next/standalone/.next/static/chunks/0s41ggdsb2alw.js +0 -3
  163. package/.next/standalone/.next/static/chunks/0t_7i~pqwbcww.js +0 -6
  164. package/.next/standalone/.next/static/chunks/0xr8w5io1-kb9.css +0 -1
  165. package/.next/standalone/.next/static/chunks/164g0yuhpb2pi.js +0 -1
  166. package/.next/standalone/components/logo.tsx +0 -36
  167. package/.next/standalone/components/theme-toggle.tsx +0 -37
  168. package/.next/standalone/contexts/ThemeContext.tsx +0 -69
  169. package/.next/standalone/failproofai-hq.gif +0 -0
  170. package/.next/standalone/public/exospheresmall-dark.png +0 -0
  171. package/.next/standalone/public/exospheresmall.png +0 -0
  172. /package/.next/standalone/.next/static/{0PSH56_4bbPBaHiyPkthl → dAuQps6jUwCz9X1Q5FFOO}/_buildManifest.js +0 -0
  173. /package/.next/standalone/.next/static/{0PSH56_4bbPBaHiyPkthl → dAuQps6jUwCz9X1Q5FFOO}/_clientMiddlewareManifest.js +0 -0
  174. /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-beta.8",
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: canonicalizeToolName(e.toolName),
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: canonicalizeToolName(e.toolName),
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. Observation-only on Pi: the agent loop has already
356
- // exited when this fires, so a deny decision cannot keep Pi running the
357
- // way Claude's exit-2-from-Stop can. We still forward so the 5
358
- // require-*-before-stop builtins run and log their findings (visible in
359
- // the dashboard's activity feed and stderr) best-effort visibility.
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
- callPolicy("agent_end", {
363
- session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
364
- cwd: resolveCwd(e.cwd),
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
  }
@@ -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-beta.8"},"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"}
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
- # Failproof AI
3
+ <img src="https://d2wq11aau0arks.cloudfront.net/failproof/logo-wordmark.png" alt="failproof ai" width="220" />
11
4
 
12
- [![Docs](https://img.shields.io/badge/docs-befailproof.ai-002CA7?style=flat-square)](https://befailproof.ai)
13
5
  [![npm](https://img.shields.io/npm/v/failproofai?style=flat-square&color=CB3837)](https://www.npmjs.com/package/failproofai)
14
- [![License](https://img.shields.io/badge/license-MIT%20%2B%20Commons%20Clause-blue?style=flat-square)](LICENSE)
15
6
  [![CI](https://img.shields.io/github/actions/workflow/status/exospherehost/failproofai/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/exospherehost/failproofai/actions)
16
7
  [![Slack](https://img.shields.io/badge/Slack-join%20us-4A154B?style=flat-square&logo=slack)](https://join.slack.com/t/failproofai/shared_invite/zt-3v63b7k5e-O3NBHmj8X6n9gZSGDx6ggQ)
8
+ [![Docs](https://img.shields.io/badge/docs-befailproof.ai-002CA7?style=flat-square)](https://docs.befailproof.ai)
9
+ [![License](https://img.shields.io/badge/license-MIT%20%2B%20Commons%20Clause-blue?style=flat-square)](./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
- **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)
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
- The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously - for **Claude Code**, **OpenAI Codex**, **GitHub Copilot CLI** _(beta)_, **Cursor Agent** _(beta)_, **OpenCode** _(beta)_, **Pi** _(beta)_, **Gemini CLI** _(beta)_ & the **Agents SDK**.
17
+ </div>
21
18
 
22
19
  <p align="center">
23
- <img src="failproofai-hq.gif" alt="Failproof AI in action" width="800" />
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
- ```bash
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
- Opens `http://localhost:8020` - browse sessions, inspect logs, manage policies.
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
- ## Policy installation
132
-
133
- ### Scopes
92
+ ## What it stops
134
93
 
135
- | Scope | Command | Where it writes |
136
- |-------|---------|-----------------|
137
- | Global (default) | `failproofai policies --install` | `~/.claude/settings.json` |
138
- | Project | `failproofai policies --install --scope project` | `.claude/settings.json` |
139
- | Local | `failproofai policies --install --scope local` | `.claude/settings.local.json` |
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
- ### Install specific policies
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
- ## Configuration
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, allow, deny, instruct } from "failproofai";
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 (!["Write", "Edit"].includes(ctx.toolName ?? "")) return allow();
244
- const path = ctx.toolInput?.file_path ?? "";
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
- Install with:
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
- | Function | Effect |
260
- |----------|--------|
127
+ | Decision | Effect |
128
+ |---|---|
261
129
  | `allow()` | Permit the operation |
262
- | `allow(message)` | Permit and send informational context to Claude |
263
- | `deny(message)` | Block the operation; message shown to Claude |
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
- Both levels load (union). Files are loaded alphabetically within each directory. Prefix with `01-`, `02-`, etc. to control order. As your team discovers new failure modes, add a policy and push — everyone gets the update on their next pull. See [examples/convention-policies/](examples/convention-policies/) for ready-to-use examples.
133
+ [Custom policies guide](https://docs.befailproof.ai/custom-policies)
294
134
 
295
135
  ---
296
136
 
297
- ## Telemetry
137
+ ## Session visibility
298
138
 
299
- Failproof AI collects anonymous usage telemetry via PostHog to understand feature usage. No session content, file names, tool inputs, or personal information is ever sent.
300
-
301
- Disable it:
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
- | Guide | Description |
312
- |-------|-------------|
313
- | [Getting Started](docs/getting-started.mdx) | Installation and first steps |
314
- | [Built-in Policies](docs/built-in-policies.mdx) | All 39 built-in policies with parameters |
315
- | [Custom Policies](docs/custom-policies.mdx) | Write your own policies |
316
- | [Configuration](docs/configuration.mdx) | Config file format and scope merging |
317
- | [Dashboard](docs/dashboard.mdx) | Monitor sessions and review policy activity |
318
- | [Architecture](docs/architecture.mdx) | How the hook system works |
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
- ## Note for failproofai contributors
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
- See [CONTRIBUTING.md](CONTRIBUTING.md).
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
- ## License
164
+ ## Contributing
353
165
 
354
- See [LICENSE](LICENSE).
166
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). New policies, edge cases, and translations all welcome.
355
167
 
356
168
  ---
357
169
 
358
- Built and Maintained by **ExosphereHost: Reliability Research Lab for Your Agents**. We help enterprises and startups improve the reliability of their AI agents through our own agents, software, and expertise. Learn more at [exosphere.host](https://exosphere.host).
170
+ Built by [Nivedit Jain](https://github.com/NiveditJain) and [Nikita Agarwal](https://github.com/nk-ag).
171
+ [befailproof.ai](https://befailproof.ai)