failproofai 0.0.2-beta.5 → 0.0.2-beta.7

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 (159) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  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 +2 -2
  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 +2 -2
  17. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  21. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  23. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  24. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  26. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  27. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  28. package/.next/standalone/.next/server/app/index.html +1 -1
  29. package/.next/standalone/.next/server/app/index.rsc +15 -15
  30. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  31. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  32. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  33. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  34. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  35. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  36. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  37. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  41. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  44. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  54. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/chunks/[root-of-the-server]__02nt~6d._.js +1 -1
  57. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_05pz9._._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00_.atk._.js → [root-of-the-server]__05zi2mt._.js} +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  64. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0gw4qdj._.js → [root-of-the-server]__0kkt_9z._.js} +2 -2
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +8 -9
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  69. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  70. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0a_7sdg.js +2 -2
  73. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0ef3uwk.js +2 -2
  74. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0j79~gv.js +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0pbja1x.js +2 -2
  76. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0r6o0i2.js +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_11y81~_.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_12or2kf.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js +1 -1
  80. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  81. package/.next/standalone/.next/server/pages/404.html +2 -2
  82. package/.next/standalone/.next/server/pages/500.html +1 -1
  83. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  84. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  85. package/.next/standalone/.next/static/chunks/{0issdwvmb81z_.js → 02u4v.k5amfah.js} +1 -1
  86. package/.next/standalone/.next/static/chunks/{031pa5~qfzt~_.js → 09e7drilkf1sn.js} +1 -1
  87. package/.next/standalone/.next/static/chunks/{14ee68i9dy9b3.js → 0bkizbynk9via.js} +1 -1
  88. package/.next/standalone/.next/static/chunks/{0gleuaabeolm~.js → 0e76l4~hq_sei.js} +1 -1
  89. package/.next/standalone/.next/static/chunks/{0odv81fzkn6u~.js → 0ltx5i0xv85_s.js} +1 -1
  90. package/.next/standalone/.next/static/chunks/{040il49xqyq~j.js → 0q7atesxo-36k.js} +1 -1
  91. package/.next/standalone/.next/static/chunks/{0ezymnwrt2x6i.js → 0suauczjqzn07.js} +1 -1
  92. package/.next/standalone/.next/static/chunks/{0d-hv1uc827s6.js → 0w.rtg9.m8dk-.js} +2 -2
  93. package/.next/standalone/.next/static/chunks/{10uhv8kh~ad6m.js → 13jdpvk~s2da8.js} +1 -1
  94. package/.next/standalone/.next/static/chunks/{turbopack-0uc5y~g6h.n7-.js → turbopack-0r26pc8h0y_-e.js} +1 -1
  95. package/.next/standalone/CHANGELOG.md +74 -0
  96. package/.next/standalone/CLAUDE.md +14 -0
  97. package/.next/standalone/README.md +20 -3
  98. package/.next/standalone/bin/failproofai.mjs +5 -0
  99. package/.next/standalone/bun.lock +31 -63
  100. package/.next/standalone/dist/cli.mjs +261 -61
  101. package/.next/standalone/docs/built-in-policies.mdx +2 -2
  102. package/.next/standalone/docs/configuration.mdx +46 -0
  103. package/.next/standalone/docs/custom-policies.mdx +63 -5
  104. package/.next/standalone/docs/docs.json +3 -3
  105. package/.next/standalone/examples/convention-policies/security-policies.mjs +40 -0
  106. package/.next/standalone/examples/convention-policies/workflow-policies.mjs +41 -0
  107. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  108. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  109. package/.next/standalone/node_modules/next/dist/compiled/jsonwebtoken/index.js +2 -2
  110. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +1 -1
  111. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +1 -1
  112. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
  113. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  114. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  115. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +7 -2
  116. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  117. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  118. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  119. package/.next/standalone/node_modules/next/dist/server/render.js +20 -19
  120. package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  121. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  122. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  123. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  124. package/.next/standalone/node_modules/next/package.json +15 -15
  125. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  126. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  127. package/.next/standalone/node_modules/react/package.json +1 -1
  128. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  129. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  130. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  131. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  132. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  133. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  134. package/.next/standalone/node_modules/react-dom/package.json +2 -2
  135. package/.next/standalone/package.json +1 -1
  136. package/.next/standalone/server.js +1 -1
  137. package/.next/standalone/src/hooks/builtin-policies.ts +110 -18
  138. package/.next/standalone/src/hooks/custom-hooks-loader.ts +158 -21
  139. package/.next/standalone/src/hooks/handler.ts +26 -6
  140. package/.next/standalone/src/hooks/hooks-config.ts +47 -2
  141. package/.next/standalone/src/hooks/llm-client.ts +2 -2
  142. package/.next/standalone/src/hooks/loader-utils.ts +4 -4
  143. package/.next/standalone/src/hooks/manager.ts +57 -14
  144. package/.next/standalone/src/hooks/policy-evaluator.ts +16 -2
  145. package/README.md +20 -3
  146. package/bin/failproofai.mjs +5 -0
  147. package/dist/cli.mjs +261 -61
  148. package/package.json +1 -1
  149. package/src/hooks/builtin-policies.ts +110 -18
  150. package/src/hooks/custom-hooks-loader.ts +158 -21
  151. package/src/hooks/handler.ts +26 -6
  152. package/src/hooks/hooks-config.ts +47 -2
  153. package/src/hooks/llm-client.ts +2 -2
  154. package/src/hooks/loader-utils.ts +4 -4
  155. package/src/hooks/manager.ts +57 -14
  156. package/src/hooks/policy-evaluator.ts +16 -2
  157. /package/.next/standalone/.next/static/{p7b7Yk0VOBDjbtr1aHDyV → Opbai6exOQP2W488FWmr6}/_buildManifest.js +0 -0
  158. /package/.next/standalone/.next/static/{p7b7Yk0VOBDjbtr1aHDyV → Opbai6exOQP2W488FWmr6}/_clientMiddlewareManifest.js +0 -0
  159. /package/.next/standalone/.next/static/{p7b7Yk0VOBDjbtr1aHDyV → Opbai6exOQP2W488FWmr6}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -149,11 +149,19 @@ function readMergedHooksConfig(cwd) {
149
149
  ...llm !== undefined ? { llm } : {}
150
150
  };
151
151
  }
152
- function getConfigPath() {
153
- return resolve(homedir2(), ".failproofai", "policies-config.json");
152
+ function getConfigPathForScope(scope, cwd) {
153
+ const base = cwd ? resolve(cwd) : process.cwd();
154
+ switch (scope) {
155
+ case "user":
156
+ return resolve(homedir2(), ".failproofai", "policies-config.json");
157
+ case "project":
158
+ return resolve(base, ".failproofai", "policies-config.json");
159
+ case "local":
160
+ return resolve(base, ".failproofai", "policies-config.local.json");
161
+ }
154
162
  }
155
- function readHooksConfig() {
156
- const configPath = getConfigPath();
163
+ function readScopedHooksConfig(scope, cwd) {
164
+ const configPath = getConfigPathForScope(scope, cwd);
157
165
  if (!existsSync2(configPath)) {
158
166
  return { enabledPolicies: [] };
159
167
  }
@@ -165,8 +173,8 @@ function readHooksConfig() {
165
173
  return { enabledPolicies: [] };
166
174
  }
167
175
  }
168
- function writeHooksConfig(config) {
169
- const configPath = getConfigPath();
176
+ function writeScopedHooksConfig(config, scope, cwd) {
177
+ const configPath = getConfigPathForScope(scope, cwd);
170
178
  const dir = dirname(configPath);
171
179
  if (!existsSync2(dir)) {
172
180
  mkdirSync2(dir, { recursive: true });
@@ -282,6 +290,37 @@ function getCurrentBranch(cwd) {
282
290
  return null;
283
291
  }
284
292
  }
293
+ function getHeadSha(cwd) {
294
+ try {
295
+ const sha = execSync("git rev-parse HEAD", {
296
+ cwd,
297
+ encoding: "utf8",
298
+ timeout: 3000
299
+ }).trim();
300
+ return sha || null;
301
+ } catch {
302
+ return null;
303
+ }
304
+ }
305
+ function getThirdPartyCheckRuns(cwd, sha) {
306
+ try {
307
+ const json = execFileSync("gh", [
308
+ "api",
309
+ `repos/{owner}/{repo}/commits/${sha}/check-runs`,
310
+ "--jq",
311
+ '.check_runs | map(select(.app.slug != "github-actions")) | map({name: .name, status: .status, conclusion: (.conclusion // "")})'
312
+ ], {
313
+ cwd,
314
+ encoding: "utf8",
315
+ timeout: 15000
316
+ }).trim();
317
+ if (!json || json === "[]")
318
+ return [];
319
+ return JSON.parse(json);
320
+ } catch {
321
+ return [];
322
+ }
323
+ }
285
324
  function matchesAllowedPattern(cmd, pattern) {
286
325
  const cmdTokens = parseArgvTokens(cmd);
287
326
  const patTokens = parseArgvTokens(pattern);
@@ -814,6 +853,20 @@ function requirePushBeforeStop(ctx) {
814
853
  const branch = getCurrentBranch(cwd);
815
854
  if (!branch || branch === "HEAD")
816
855
  return allow("Detached HEAD, skipping push check.");
856
+ const baseBranch = ctx.params?.baseBranch ?? "main";
857
+ if (branch === baseBranch) {
858
+ return allow(`On base branch "${baseBranch}", skipping push check.`);
859
+ }
860
+ try {
861
+ const ahead = execFileSync("git", ["log", `${remote}/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
862
+ if (!ahead) {
863
+ return allow(`No commits ahead of ${remote}/${baseBranch}, skipping push check.`);
864
+ }
865
+ const diff = execFileSync("git", ["diff", "--stat", `${remote}/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", timeout: 5000 }).trim();
866
+ if (!diff) {
867
+ return allow(`No file changes compared to ${remote}/${baseBranch}, skipping push check.`);
868
+ }
869
+ } catch {}
817
870
  let hasTracking = false;
818
871
  try {
819
872
  execFileSync("git", ["rev-parse", "--verify", `${remote}/${branch}`], {
@@ -900,22 +953,27 @@ function requireCiGreenBeforeStop(ctx) {
900
953
  const branch = getCurrentBranch(cwd);
901
954
  if (!branch || branch === "HEAD")
902
955
  return allow("Detached HEAD, skipping CI check.");
903
- const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"], {
904
- cwd,
905
- encoding: "utf8",
906
- timeout: 15000
907
- }).trim();
908
- if (!runsJson || runsJson === "[]")
909
- return allow(`No CI runs found for branch "${branch}".`);
910
- const runs = JSON.parse(runsJson);
911
- if (runs.length === 0)
956
+ let workflowRuns = [];
957
+ try {
958
+ const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"], { cwd, encoding: "utf8", timeout: 15000 }).trim();
959
+ if (runsJson && runsJson !== "[]") {
960
+ workflowRuns = JSON.parse(runsJson);
961
+ }
962
+ } catch {}
963
+ let thirdPartyChecks = [];
964
+ const sha = getHeadSha(cwd);
965
+ if (sha) {
966
+ thirdPartyChecks = getThirdPartyCheckRuns(cwd, sha);
967
+ }
968
+ const allChecks = [...workflowRuns, ...thirdPartyChecks];
969
+ if (allChecks.length === 0)
912
970
  return allow(`No CI runs found for branch "${branch}".`);
913
- const failing = runs.filter((r) => r.status === "completed" && r.conclusion !== "success" && r.conclusion !== "skipped");
971
+ const failing = allChecks.filter((r) => r.status === "completed" && r.conclusion !== "success" && r.conclusion !== "skipped");
914
972
  if (failing.length > 0) {
915
973
  const names = failing.map((r) => `"${r.name}"`).join(", ");
916
974
  return deny(`CI checks are failing on branch "${branch}": ${names}. Fix the failing checks before stopping.`);
917
975
  }
918
- const pending = runs.filter((r) => r.status === "in_progress" || r.status === "queued" || r.status === "waiting");
976
+ const pending = allChecks.filter((r) => r.status === "in_progress" || r.status === "queued" || r.status === "waiting");
919
977
  if (pending.length > 0) {
920
978
  const names = pending.map((r) => `"${r.name}"`).join(", ");
921
979
  return deny(`CI checks are still running on branch "${branch}": ${names}. Wait for all checks to complete and verify they pass.`);
@@ -1283,6 +1341,11 @@ var init_builtin_policies = __esm(() => {
1283
1341
  type: "string",
1284
1342
  description: "Remote name to push to (default: origin)",
1285
1343
  default: "origin"
1344
+ },
1345
+ baseBranch: {
1346
+ type: "string",
1347
+ description: "Base branch to compare against (default: main)",
1348
+ default: "main"
1286
1349
  }
1287
1350
  }
1288
1351
  },
@@ -1315,6 +1378,15 @@ var init_builtin_policies = __esm(() => {
1315
1378
  });
1316
1379
 
1317
1380
  // src/hooks/policy-evaluator.ts
1381
+ function appendHint(baseReason, hint) {
1382
+ const base = baseReason.trim();
1383
+ const normalizedHint = typeof hint === "string" ? hint.trim() : "";
1384
+ if (!normalizedHint)
1385
+ return base;
1386
+ if (!base)
1387
+ return normalizedHint;
1388
+ return `${base}. ${normalizedHint}`;
1389
+ }
1318
1390
  async function evaluatePolicies(eventType, payload, session, config) {
1319
1391
  const toolName = payload.tool_name;
1320
1392
  const toolInput = payload.tool_input;
@@ -1354,7 +1426,7 @@ async function evaluatePolicies(eventType, payload, session, config) {
1354
1426
  continue;
1355
1427
  }
1356
1428
  if (result.decision === "deny") {
1357
- const reason = result.reason ?? `Blocked by policy: ${policy.name}`;
1429
+ const reason = appendHint(result.reason ?? `Blocked by policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1358
1430
  hookLogInfo(`deny by "${policy.name}": ${reason}`);
1359
1431
  const displayTool = ctx.toolName ?? "unknown tool";
1360
1432
  if (eventType === "PreToolUse") {
@@ -1401,7 +1473,7 @@ async function evaluatePolicies(eventType, payload, session, config) {
1401
1473
  }
1402
1474
  if (result.decision === "instruct" && !instructPolicyName) {
1403
1475
  instructPolicyName = policy.name;
1404
- instructReason = result.reason ?? `Instruction from policy: ${policy.name}`;
1476
+ instructReason = appendHint(result.reason ?? `Instruction from policy: ${policy.name}`, config?.policyParams?.[policy.name]?.hint);
1405
1477
  hookLogInfo(`instruct by "${policy.name}": ${instructReason}`);
1406
1478
  }
1407
1479
  if (result.decision === "allow" && result.reason) {
@@ -1514,10 +1586,9 @@ async function createEsmShim(distIndex, distUrl) {
1514
1586
  const shimPath = distIndex + ".__failproofai_esm_shim__.mjs";
1515
1587
  const shimCode = [
1516
1588
  `import _cjs from '${distUrl}';`,
1517
- `export const createApp = _cjs.createApp;`,
1518
- `export const getQueueCondition = _cjs.getQueueCondition;`,
1519
- `export const clearQueueCondition = _cjs.clearQueueCondition;`,
1520
1589
  `export const customPolicies = _cjs.customPolicies;`,
1590
+ `export const getCustomHooks = _cjs.getCustomHooks;`,
1591
+ `export const clearCustomHooks = _cjs.clearCustomHooks;`,
1521
1592
  `export const allow = _cjs.allow;`,
1522
1593
  `export const deny = _cjs.deny;`,
1523
1594
  `export const instruct = _cjs.instruct;`,
@@ -1597,20 +1668,21 @@ var init_loader_utils = __esm(() => {
1597
1668
  });
1598
1669
 
1599
1670
  // src/hooks/custom-hooks-loader.ts
1600
- import { resolve as resolve4, isAbsolute } from "node:path";
1601
- import { existsSync as existsSync3 } from "node:fs";
1671
+ import { resolve as resolve4, isAbsolute, basename } from "node:path";
1672
+ import { existsSync as existsSync3, readdirSync } from "node:fs";
1602
1673
  import { pathToFileURL as pathToFileURL2 } from "node:url";
1603
- async function loadCustomHooks(customPoliciesPath, opts) {
1604
- if (!customPoliciesPath)
1674
+ import { homedir as homedir4 } from "node:os";
1675
+ function discoverPolicyFiles(dir) {
1676
+ if (!existsSync3(dir))
1605
1677
  return [];
1606
- const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(process.cwd(), customPoliciesPath);
1607
- if (!existsSync3(absPath)) {
1608
- if (opts?.strict)
1609
- throw new Error(`Custom hooks file not found: ${absPath}`);
1610
- hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1678
+ try {
1679
+ const entries = readdirSync(dir, { withFileTypes: true });
1680
+ return entries.filter((e) => e.isFile() && CONVENTION_FILE_RE.test(e.name)).sort((a, b) => a.name.localeCompare(b.name)).map((e) => resolve4(dir, e.name));
1681
+ } catch {
1611
1682
  return [];
1612
1683
  }
1613
- clearCustomHooks();
1684
+ }
1685
+ async function loadSingleFile(absPath, opts) {
1614
1686
  const g = globalThis;
1615
1687
  g[LOADING_KEY] = true;
1616
1688
  let tmpFiles = [];
@@ -1626,18 +1698,87 @@ async function loadCustomHooks(customPoliciesPath, opts) {
1626
1698
  if (opts?.strict)
1627
1699
  throw new Error(`Failed to load custom hooks from ${absPath}: ${msg}`);
1628
1700
  hookLogError(`failed to load custom hooks from ${absPath}: ${msg}`);
1629
- return [];
1630
1701
  } finally {
1631
1702
  g[LOADING_KEY] = false;
1632
1703
  await cleanupTmpFiles(tmpFiles);
1633
1704
  }
1705
+ }
1706
+ async function loadCustomHooks(customPoliciesPath, opts) {
1707
+ if (!customPoliciesPath)
1708
+ return [];
1709
+ const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1710
+ if (!existsSync3(absPath)) {
1711
+ if (opts?.strict)
1712
+ throw new Error(`Custom hooks file not found: ${absPath}`);
1713
+ hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1714
+ return [];
1715
+ }
1716
+ clearCustomHooks();
1717
+ await loadSingleFile(absPath, opts);
1634
1718
  return getCustomHooks();
1635
1719
  }
1636
- var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__";
1720
+ async function loadAllCustomHooks(customPoliciesPath, opts) {
1721
+ clearCustomHooks();
1722
+ const conventionSources = [];
1723
+ if (customPoliciesPath) {
1724
+ const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(opts?.sessionCwd ?? process.cwd(), customPoliciesPath);
1725
+ if (existsSync3(absPath)) {
1726
+ await loadSingleFile(absPath);
1727
+ } else {
1728
+ hookLogWarn(`customPoliciesPath not found: ${absPath}`);
1729
+ }
1730
+ }
1731
+ const hooksBeforeConvention = getCustomHooks().length;
1732
+ const projectDir = resolve4(opts?.sessionCwd ?? process.cwd(), ".failproofai", "policies");
1733
+ const projectFiles = discoverPolicyFiles(projectDir);
1734
+ for (const file of projectFiles) {
1735
+ const hooksBefore = getCustomHooks().length;
1736
+ await loadSingleFile(file);
1737
+ const newHooks = getCustomHooks().slice(hooksBefore);
1738
+ if (newHooks.length > 0) {
1739
+ conventionSources.push({
1740
+ scope: "project",
1741
+ file: basename(file),
1742
+ hookNames: newHooks.map((h) => h.name)
1743
+ });
1744
+ }
1745
+ }
1746
+ const userDir = resolve4(homedir4(), ".failproofai", "policies");
1747
+ const userFiles = discoverPolicyFiles(userDir);
1748
+ for (const file of userFiles) {
1749
+ const hooksBefore = getCustomHooks().length;
1750
+ await loadSingleFile(file);
1751
+ const newHooks = getCustomHooks().slice(hooksBefore);
1752
+ if (newHooks.length > 0) {
1753
+ conventionSources.push({
1754
+ scope: "user",
1755
+ file: basename(file),
1756
+ hookNames: newHooks.map((h) => h.name)
1757
+ });
1758
+ }
1759
+ }
1760
+ const allHooks = getCustomHooks();
1761
+ const conventionCount = allHooks.length - hooksBeforeConvention;
1762
+ if (projectFiles.length > 0 || userFiles.length > 0) {
1763
+ hookLogInfo(`convention policies: ${projectFiles.length} project file(s), ${userFiles.length} user file(s), ${conventionCount} hook(s)`);
1764
+ }
1765
+ const conventionHookRefs = new Set;
1766
+ for (const hook of allHooks.slice(hooksBeforeConvention)) {
1767
+ conventionHookRefs.add(hook);
1768
+ }
1769
+ for (const hook of allHooks) {
1770
+ if (conventionHookRefs.has(hook)) {
1771
+ hook.__conventionSource = true;
1772
+ }
1773
+ }
1774
+ return { hooks: allHooks, conventionSources };
1775
+ }
1776
+ var LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__", CONVENTION_FILE_RE;
1637
1777
  var init_custom_hooks_loader = __esm(() => {
1638
1778
  init_hook_logger();
1639
1779
  init_custom_hooks_registry();
1640
1780
  init_loader_utils();
1781
+ CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
1641
1782
  });
1642
1783
 
1643
1784
  // src/hooks/hook-activity-store.ts
@@ -1646,14 +1787,14 @@ import {
1646
1787
  writeFileSync as writeFileSync2,
1647
1788
  appendFileSync as appendFileSync2,
1648
1789
  renameSync as renameSync2,
1649
- readdirSync,
1790
+ readdirSync as readdirSync2,
1650
1791
  mkdirSync as mkdirSync3,
1651
1792
  existsSync as existsSync4,
1652
1793
  statSync as statSync2,
1653
1794
  unlinkSync
1654
1795
  } from "node:fs";
1655
1796
  import { join as join3 } from "node:path";
1656
- import { homedir as homedir4 } from "node:os";
1797
+ import { homedir as homedir5 } from "node:os";
1657
1798
  function ensureDir() {
1658
1799
  if (!existsSync4(storeDir)) {
1659
1800
  mkdirSync3(storeDir, { recursive: true });
@@ -1758,12 +1899,12 @@ function updateStats(entry) {
1758
1899
  }
1759
1900
  var PAGE_SIZE = 25, DEFAULT_STORE_DIR, CURRENT_FILE = "current.jsonl", COUNT_FILE = "current.count", STATS_FILE = "stats.json", LOCK_FILE = "current.lock", LOCK_STALE_MS = 2000, storeDir, rotateSeq = 0;
1760
1901
  var init_hook_activity_store = __esm(() => {
1761
- DEFAULT_STORE_DIR = join3(homedir4(), ".failproofai", "cache", "hook-activity");
1902
+ DEFAULT_STORE_DIR = join3(homedir5(), ".failproofai", "cache", "hook-activity");
1762
1903
  storeDir = DEFAULT_STORE_DIR;
1763
1904
  });
1764
1905
 
1765
1906
  // package.json
1766
- var version2 = "0.0.2-beta.5";
1907
+ var version2 = "0.0.2-beta.7";
1767
1908
  var init_package = () => {};
1768
1909
 
1769
1910
  // src/posthog-key.ts
@@ -1925,9 +2066,13 @@ async function handleHookEvent(eventType) {
1925
2066
  const config = readMergedHooksConfig(session.cwd);
1926
2067
  clearPolicies();
1927
2068
  registerBuiltinPolicies(config.enabledPolicies);
1928
- const customHooksList = await loadCustomHooks(config.customPoliciesPath);
2069
+ const loadResult = await loadAllCustomHooks(config.customPoliciesPath, { sessionCwd: session.cwd });
2070
+ const customHooksList = loadResult.hooks;
2071
+ const conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
1929
2072
  for (const hook of customHooksList) {
1930
2073
  const hookName = hook.name;
2074
+ const isConvention = hook.__conventionSource === true;
2075
+ const prefix = isConvention ? "convention" : "custom";
1931
2076
  const fn = async (ctx) => {
1932
2077
  try {
1933
2078
  const result2 = await Promise.race([
@@ -1938,16 +2083,17 @@ async function handleHookEvent(eventType) {
1938
2083
  } catch (err) {
1939
2084
  const msg = err instanceof Error ? err.message : String(err);
1940
2085
  const isTimeout = msg === "timeout";
1941
- hookLogWarn(`custom hook "${hookName}" failed: ${msg}`);
2086
+ hookLogWarn(`${prefix} hook "${hookName}" failed: ${msg}`);
1942
2087
  trackHookEvent(getInstanceId(), "custom_hook_error", {
1943
2088
  hook_name: hookName,
1944
2089
  error_type: isTimeout ? "timeout" : "exception",
1945
- event_type: eventType
2090
+ event_type: eventType,
2091
+ is_convention_policy: isConvention
1946
2092
  });
1947
2093
  return { decision: "allow" };
1948
2094
  }
1949
2095
  };
1950
- registerPolicy(`custom/${hookName}`, hook.description ?? "", fn, hook.match ?? {}, -1);
2096
+ registerPolicy(`${prefix}/${hookName}`, hook.description ?? "", fn, hook.match ?? {}, -1);
1951
2097
  }
1952
2098
  if (customHooksList.length > 0) {
1953
2099
  trackHookEvent(getInstanceId(), "custom_hooks_loaded", {
@@ -1956,7 +2102,16 @@ async function handleHookEvent(eventType) {
1956
2102
  event_types_covered: [...new Set(customHooksList.flatMap((h) => h.match?.events ?? []))]
1957
2103
  });
1958
2104
  }
1959
- hookLogInfo(`event=${eventType} policies=${config.enabledPolicies.length} custom=${customHooksList.length}`);
2105
+ if (loadResult.conventionSources.length > 0) {
2106
+ trackHookEvent(getInstanceId(), "convention_policies_loaded", {
2107
+ event_type: eventType,
2108
+ project_file_count: loadResult.conventionSources.filter((s) => s.scope === "project").length,
2109
+ user_file_count: loadResult.conventionSources.filter((s) => s.scope === "user").length,
2110
+ convention_hook_count: conventionHookNames.size,
2111
+ convention_hook_names: [...conventionHookNames]
2112
+ });
2113
+ }
2114
+ hookLogInfo(`event=${eventType} policies=${config.enabledPolicies.length} custom=${customHooksList.length} convention=${conventionHookNames.size}`);
1960
2115
  const result = await evaluatePolicies(eventType, parsed, session, config);
1961
2116
  const durationMs = Math.round(performance.now() - startTime);
1962
2117
  hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
@@ -1988,7 +2143,8 @@ async function handleHookEvent(eventType) {
1988
2143
  if (result.decision === "deny" || result.decision === "instruct") {
1989
2144
  try {
1990
2145
  const isCustomHook = result.policyName?.startsWith("custom/") ?? false;
1991
- const hasCustomParams = !isCustomHook && !!(result.policyName && config.policyParams?.[result.policyName]);
2146
+ const isConventionPolicy = result.policyName?.startsWith("convention/") ?? false;
2147
+ const hasCustomParams = !isCustomHook && !isConventionPolicy && !!(result.policyName && config.policyParams?.[result.policyName]);
1992
2148
  const paramKeysOverridden = hasCustomParams ? Object.keys(config.policyParams[result.policyName]) : [];
1993
2149
  const distinctId = getInstanceId();
1994
2150
  await trackHookEvent(distinctId, "hook_policy_triggered", {
@@ -1997,6 +2153,7 @@ async function handleHookEvent(eventType) {
1997
2153
  policy_name: result.policyName,
1998
2154
  decision: result.decision,
1999
2155
  is_custom_hook: isCustomHook,
2156
+ is_convention_policy: isConventionPolicy,
2000
2157
  has_custom_params: hasCustomParams,
2001
2158
  param_keys_overridden: paramKeysOverridden
2002
2159
  });
@@ -2329,13 +2486,13 @@ __export(exports_manager, {
2329
2486
  });
2330
2487
  import { execSync as execSync3 } from "node:child_process";
2331
2488
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
2332
- import { resolve as resolve5, dirname as dirname3 } from "node:path";
2333
- import { homedir as homedir5, platform, arch, release, hostname } from "node:os";
2489
+ import { resolve as resolve5, dirname as dirname3, basename as basename2 } from "node:path";
2490
+ import { homedir as homedir6, platform, arch, release, hostname } from "node:os";
2334
2491
  function getSettingsPath(scope, cwd) {
2335
2492
  const base = cwd ? resolve5(cwd) : process.cwd();
2336
2493
  switch (scope) {
2337
2494
  case "user":
2338
- return resolve5(homedir5(), ".claude", "settings.json");
2495
+ return resolve5(homedir6(), ".claude", "settings.json");
2339
2496
  case "project":
2340
2497
  return resolve5(base, ".claude", "settings.json");
2341
2498
  case "local":
@@ -2462,7 +2619,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
2462
2619
  }
2463
2620
  }
2464
2621
  const binaryPath = resolveFailproofaiBinary();
2465
- const previousConfig = readHooksConfig();
2622
+ const previousConfig = readScopedHooksConfig(scope, cwd);
2466
2623
  const previousEnabled = new Set(previousConfig.enabledPolicies);
2467
2624
  let selectedPolicies;
2468
2625
  if (policyNames !== undefined) {
@@ -2496,7 +2653,7 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
2496
2653
  console.log(`
2497
2654
  Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
2498
2655
  }
2499
- writeHooksConfig(configToWrite);
2656
+ writeScopedHooksConfig(configToWrite, scope, cwd);
2500
2657
  console.log(`
2501
2658
  Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`);
2502
2659
  if (removeCustomHooks) {
@@ -2573,15 +2730,16 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`)
2573
2730
  }
2574
2731
  }
2575
2732
  async function removeHooks(policyNames, scope = "user", cwd, opts) {
2733
+ const configScope = scope === "all" ? "user" : scope;
2576
2734
  if (opts?.removeCustomHooks) {
2577
- const config = readHooksConfig();
2735
+ const config = readScopedHooksConfig(configScope, cwd);
2578
2736
  delete config.customPoliciesPath;
2579
- writeHooksConfig(config);
2737
+ writeScopedHooksConfig(config, configScope, cwd);
2580
2738
  console.log("Custom hooks path cleared.");
2581
2739
  }
2582
2740
  if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
2583
2741
  validatePolicyNames(policyNames);
2584
- const config = readHooksConfig();
2742
+ const config = readScopedHooksConfig(configScope, cwd);
2585
2743
  const removeSet = new Set(policyNames);
2586
2744
  const remaining = config.enabledPolicies.filter((p) => !removeSet.has(p));
2587
2745
  const notEnabled = policyNames.filter((p) => !config.enabledPolicies.includes(p));
@@ -2595,7 +2753,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
2595
2753
  enabledPolicies: remaining,
2596
2754
  ...filteredParams && Object.keys(filteredParams).length > 0 ? { policyParams: filteredParams } : {}
2597
2755
  };
2598
- writeHooksConfig(updatedConfig);
2756
+ writeScopedHooksConfig(updatedConfig, configScope, cwd);
2599
2757
  try {
2600
2758
  const distinctId = getInstanceId();
2601
2759
  const actuallyRemoved = policyNames.filter((p) => config.enabledPolicies.includes(p));
@@ -2616,7 +2774,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
2616
2774
  console.log(`Remaining: ${remaining.length > 0 ? remaining.join(", ") : "(none)"}`);
2617
2775
  return;
2618
2776
  }
2619
- const configBeforeRemoval = readHooksConfig();
2777
+ const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
2620
2778
  const scopesToRemove = scope === "all" ? [...HOOK_SCOPES] : [scope];
2621
2779
  let totalRemoved = 0;
2622
2780
  for (const s of scopesToRemove) {
@@ -2663,10 +2821,18 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
2663
2821
  hostname_hash: hashToId(hostname())
2664
2822
  });
2665
2823
  } catch {}
2666
- if (scope === "all" || !HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
2667
- const existingForClear = readHooksConfig();
2668
- const { customPoliciesPath: _drop, policyParams: _dropParams, ...restClear } = existingForClear;
2669
- writeHooksConfig({ ...restClear, enabledPolicies: [] });
2824
+ if (scope === "all") {
2825
+ for (const s of HOOK_SCOPES) {
2826
+ const existing = readScopedHooksConfig(s, cwd);
2827
+ if (existing.enabledPolicies.length > 0 || existing.customPoliciesPath || existing.policyParams) {
2828
+ const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
2829
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
2830
+ }
2831
+ }
2832
+ } else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
2833
+ const existing = readScopedHooksConfig(configScope, cwd);
2834
+ const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
2835
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
2670
2836
  }
2671
2837
  }
2672
2838
  async function listHooks(cwd) {
@@ -2803,6 +2969,35 @@ Failproof AI Hook Policies
2803
2969
  }
2804
2970
  console.log();
2805
2971
  }
2972
+ const base = cwd ? resolve5(cwd) : process.cwd();
2973
+ const conventionDirs = [
2974
+ { label: "Project", dir: resolve5(base, ".failproofai", "policies") },
2975
+ { label: "User", dir: resolve5(homedir6(), ".failproofai", "policies") }
2976
+ ];
2977
+ for (const { label, dir } of conventionDirs) {
2978
+ const files = discoverPolicyFiles(dir);
2979
+ if (files.length === 0)
2980
+ continue;
2981
+ console.log(`
2982
+ ── Convention Policies — ${label} (${dir}) ──────────`);
2983
+ for (const file of files) {
2984
+ try {
2985
+ const hooks = await loadCustomHooks(file);
2986
+ if (hooks.length === 0) {
2987
+ const filename = basename2(file);
2988
+ console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
2989
+ } else {
2990
+ const filename = basename2(file);
2991
+ const hookSummary = hooks.map((h) => h.name).join(", ");
2992
+ console.log(` \x1B[32m✓\x1B[0m ${filename.padEnd(nameColWidth)}${hooks.length} hook(s): ${hookSummary}`);
2993
+ }
2994
+ } catch {
2995
+ const filename = basename2(file);
2996
+ console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31merror\x1B[0m`);
2997
+ }
2998
+ }
2999
+ console.log();
3000
+ }
2806
3001
  }
2807
3002
  var VALID_POLICY_NAMES;
2808
3003
  var init_manager = __esm(() => {
@@ -2818,10 +3013,10 @@ var init_manager = __esm(() => {
2818
3013
  });
2819
3014
 
2820
3015
  // lib/paths.ts
2821
- import { homedir as homedir6 } from "os";
3016
+ import { homedir as homedir7 } from "os";
2822
3017
  import { join as join4 } from "path";
2823
3018
  function getDefaultClaudeProjectsPath() {
2824
- return join4(homedir6(), ".claude", "projects");
3019
+ return join4(homedir7(), ".claude", "projects");
2825
3020
  }
2826
3021
  var init_paths = () => {};
2827
3022
 
@@ -2990,7 +3185,7 @@ import { realpathSync as realpathSync2 } from "fs";
2990
3185
  import { dirname as dirname5, resolve as resolve8 } from "path";
2991
3186
  import { fileURLToPath as fileURLToPath2 } from "url";
2992
3187
  // package.json
2993
- var version = "0.0.2-beta.5";
3188
+ var version = "0.0.2-beta.7";
2994
3189
 
2995
3190
  // bin/failproofai.mjs
2996
3191
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
@@ -3054,6 +3249,11 @@ COMMANDS
3054
3249
  --version, -v Print version and exit
3055
3250
  --help, -h Show this help message
3056
3251
 
3252
+ CONVENTION POLICIES
3253
+ Drop *policies.{js,mjs,ts} files into .failproofai/policies/ for auto-loading.
3254
+ Works at project level (.failproofai/policies/) and user level (~/.failproofai/policies/).
3255
+ No --custom flag or config changes needed \u2014 just drop files and they're picked up.
3256
+
3057
3257
  EXAMPLES
3058
3258
  failproofai policies
3059
3259
  failproofai policies --install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "0.0.2-beta.5",
3
+ "version": "0.0.2-beta.7",
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"