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
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { execSync } from "node:child_process";
5
5
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
6
- import { resolve, dirname } from "node:path";
6
+ import { resolve, dirname, basename } from "node:path";
7
7
  import { homedir, platform, arch, release, hostname } from "node:os";
8
8
  import {
9
9
  HOOK_EVENT_TYPES,
@@ -15,10 +15,10 @@ import {
15
15
  type ClaudeSettings,
16
16
  } from "./types";
17
17
  import { promptPolicySelection } from "./install-prompt";
18
- import { readHooksConfig, writeHooksConfig, readMergedHooksConfig } from "./hooks-config";
18
+ import { readMergedHooksConfig, readScopedHooksConfig, writeScopedHooksConfig } from "./hooks-config";
19
19
  import type { HooksConfig } from "./policy-types";
20
20
  import { BUILTIN_POLICIES } from "./builtin-policies";
21
- import { loadCustomHooks } from "./custom-hooks-loader";
21
+ import { loadCustomHooks, discoverPolicyFiles } from "./custom-hooks-loader";
22
22
  import { trackHookEvent } from "./hook-telemetry";
23
23
  import { getInstanceId, hashToId } from "../../lib/telemetry-id";
24
24
  import { CliError } from "../cli-error";
@@ -203,7 +203,7 @@ export async function installHooks(
203
203
  const binaryPath = resolveFailproofaiBinary();
204
204
 
205
205
  // Capture existing config before overwriting (used for telemetry diff)
206
- const previousConfig = readHooksConfig();
206
+ const previousConfig = readScopedHooksConfig(scope, cwd);
207
207
  const previousEnabled = new Set(previousConfig.enabledPolicies);
208
208
 
209
209
  let selectedPolicies: string[];
@@ -251,7 +251,7 @@ export async function installHooks(
251
251
  `\nValidated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`,
252
252
  );
253
253
  }
254
- writeHooksConfig(configToWrite);
254
+ writeScopedHooksConfig(configToWrite, scope, cwd);
255
255
  console.log(`\nEnabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}`);
256
256
  if (removeCustomHooks) {
257
257
  console.log("Custom hooks path cleared.");
@@ -355,18 +355,21 @@ export async function installHooks(
355
355
  * @param opts.betaOnly — set to true when removing only beta policies (adds beta_only flag to telemetry)
356
356
  */
357
357
  export async function removeHooks(policyNames?: string[], scope: HookScope | "all" = "user", cwd?: string, opts?: { betaOnly?: boolean; source?: string; removeCustomHooks?: boolean }): Promise<void> {
358
+ // Resolve the effective config scope ("all" falls back to "user" for config reads/writes)
359
+ const configScope: HookScope = scope === "all" ? "user" : scope;
360
+
358
361
  // Clear custom hooks path if requested
359
362
  if (opts?.removeCustomHooks) {
360
- const config = readHooksConfig();
363
+ const config = readScopedHooksConfig(configScope, cwd);
361
364
  delete config.customPoliciesPath;
362
- writeHooksConfig(config);
365
+ writeScopedHooksConfig(config, configScope, cwd);
363
366
  console.log("Custom hooks path cleared.");
364
367
  }
365
368
 
366
369
  // Remove specific policies from config (keep hooks installed)
367
370
  if (policyNames && policyNames.length > 0 && !(policyNames.length === 1 && policyNames[0] === "all")) {
368
371
  validatePolicyNames(policyNames);
369
- const config = readHooksConfig();
372
+ const config = readScopedHooksConfig(configScope, cwd);
370
373
  const removeSet = new Set(policyNames);
371
374
  const remaining = config.enabledPolicies.filter((p) => !removeSet.has(p));
372
375
  const notEnabled = policyNames.filter((p) => !config.enabledPolicies.includes(p));
@@ -382,7 +385,7 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
382
385
  enabledPolicies: remaining,
383
386
  ...(filteredParams && Object.keys(filteredParams).length > 0 ? { policyParams: filteredParams } : {}),
384
387
  };
385
- writeHooksConfig(updatedConfig);
388
+ writeScopedHooksConfig(updatedConfig, configScope, cwd);
386
389
 
387
390
  // Telemetry: track policy-only removal from config
388
391
  try {
@@ -410,7 +413,7 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
410
413
  }
411
414
 
412
415
  // Capture enabled policies before clearing (used for accurate telemetry below)
413
- const configBeforeRemoval = readHooksConfig();
416
+ const configBeforeRemoval = readScopedHooksConfig(configScope, cwd);
414
417
 
415
418
  // Remove all failproofai hooks from Claude Code settings
416
419
  const scopesToRemove: HookScope[] = scope === "all" ? [...HOOK_SCOPES] : [scope];
@@ -472,10 +475,19 @@ export async function removeHooks(policyNames?: string[], scope: HookScope | "al
472
475
  }
473
476
 
474
477
  // Clear policy config when removing from all scopes, or when no hooks remain in any scope
475
- if (scope === "all" || !HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
476
- const existingForClear = readHooksConfig();
477
- const { customPoliciesPath: _drop, policyParams: _dropParams, ...restClear } = existingForClear;
478
- writeHooksConfig({ ...restClear, enabledPolicies: [] });
478
+ if (scope === "all") {
479
+ // Clear config across all three scopes
480
+ for (const s of HOOK_SCOPES) {
481
+ const existing = readScopedHooksConfig(s, cwd);
482
+ if (existing.enabledPolicies.length > 0 || existing.customPoliciesPath || existing.policyParams) {
483
+ const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
484
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
485
+ }
486
+ }
487
+ } else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
488
+ const existing = readScopedHooksConfig(configScope, cwd);
489
+ const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
490
+ writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
479
491
  }
480
492
  }
481
493
 
@@ -638,4 +650,35 @@ export async function listHooks(cwd?: string): Promise<void> {
638
650
  }
639
651
  console.log();
640
652
  }
653
+
654
+ // Convention Policies section (.failproofai/policies/*policies.{js,mjs,ts})
655
+ const base = cwd ? resolve(cwd) : process.cwd();
656
+ const conventionDirs: { label: string; dir: string }[] = [
657
+ { label: "Project", dir: resolve(base, ".failproofai", "policies") },
658
+ { label: "User", dir: resolve(homedir(), ".failproofai", "policies") },
659
+ ];
660
+
661
+ for (const { label, dir } of conventionDirs) {
662
+ const files = discoverPolicyFiles(dir);
663
+ if (files.length === 0) continue;
664
+
665
+ console.log(`\n \u2500\u2500 Convention Policies \u2014 ${label} (${dir}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
666
+ for (const file of files) {
667
+ try {
668
+ const hooks = await loadCustomHooks(file);
669
+ if (hooks.length === 0) {
670
+ const filename = basename(file);
671
+ console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31mfailed to load\x1B[0m`);
672
+ } else {
673
+ const filename = basename(file);
674
+ const hookSummary = hooks.map((h) => h.name).join(", ");
675
+ console.log(` \x1B[32m\u2713\x1B[0m ${filename.padEnd(nameColWidth)}${hooks.length} hook(s): ${hookSummary}`);
676
+ }
677
+ } catch {
678
+ const filename = basename(file);
679
+ console.log(` \x1B[31m\u2717\x1B[0m ${filename.padEnd(nameColWidth)}\x1B[31merror\x1B[0m`);
680
+ }
681
+ }
682
+ console.log();
683
+ }
641
684
  }
@@ -8,6 +8,14 @@ import { BUILTIN_POLICIES } from "./builtin-policies";
8
8
  import { getPoliciesForEvent } from "./policy-registry";
9
9
  import { hookLogInfo, hookLogWarn } from "./hook-logger";
10
10
 
11
+ function appendHint(baseReason: string, hint: unknown): string {
12
+ const base = baseReason.trim();
13
+ const normalizedHint = typeof hint === "string" ? hint.trim() : "";
14
+ if (!normalizedHint) return base;
15
+ if (!base) return normalizedHint;
16
+ return `${base}. ${normalizedHint}`;
17
+ }
18
+
11
19
  export interface EvaluationResult {
12
20
  exitCode: number;
13
21
  stdout: string;
@@ -80,7 +88,10 @@ export async function evaluatePolicies(
80
88
  }
81
89
 
82
90
  if (result.decision === "deny") {
83
- const reason = result.reason ?? `Blocked by policy: ${policy.name}`;
91
+ const reason = appendHint(
92
+ result.reason ?? `Blocked by policy: ${policy.name}`,
93
+ config?.policyParams?.[policy.name]?.hint,
94
+ );
84
95
  hookLogInfo(`deny by "${policy.name}": ${reason}`);
85
96
 
86
97
  const displayTool = ctx.toolName ?? "unknown tool";
@@ -134,7 +145,10 @@ export async function evaluatePolicies(
134
145
  // Accumulate first instruct (does not short-circuit — later policies can still deny)
135
146
  if (result.decision === "instruct" && !instructPolicyName) {
136
147
  instructPolicyName = policy.name;
137
- instructReason = result.reason ?? `Instruction from policy: ${policy.name}`;
148
+ instructReason = appendHint(
149
+ result.reason ?? `Instruction from policy: ${policy.name}`,
150
+ config?.policyParams?.[policy.name]?.hint,
151
+ );
138
152
  hookLogInfo(`instruct by "${policy.name}": ${instructReason}`);
139
153
  }
140
154
 
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  [![npm](https://img.shields.io/npm/v/failproofai?style=flat-square&color=CB3837)](https://www.npmjs.com/package/failproofai)
14
14
  [![License](https://img.shields.io/badge/license-MIT%20%2B%20Commons%20Clause-blue?style=flat-square)](LICENSE)
15
15
  [![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
- [![Discord](https://img.shields.io/discord/1234567890?style=flat-square&label=Discord&color=5865F2)](https://discord.com/invite/zT92CAgvkj)
16
+ [![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)
17
17
 
18
18
  The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously - for **Claude Code** & the **Agents SDK**.
19
19
 
@@ -111,10 +111,12 @@ Policy configuration lives in `~/.failproofai/policies-config.json` (global) or
111
111
  ],
112
112
  "policyParams": {
113
113
  "block-sudo": {
114
- "allowPatterns": ["sudo systemctl status", "sudo journalctl"]
114
+ "allowPatterns": ["sudo systemctl status", "sudo journalctl"],
115
+ "hint": "Use apt-get directly without sudo."
115
116
  },
116
117
  "block-push-master": {
117
- "protectedBranches": ["main", "release", "prod"]
118
+ "protectedBranches": ["main", "release", "prod"],
119
+ "hint": "Try creating a fresh branch instead."
118
120
  },
119
121
  "sanitize-api-keys": {
120
122
  "additionalPatterns": [
@@ -216,6 +218,21 @@ failproofai policies --install --custom ./my-policies.js
216
218
 
217
219
  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.
218
220
 
221
+ ### Convention-based policies (v0.0.2-beta.7+)
222
+
223
+ Drop `*policies.{js,mjs,ts}` files into `.failproofai/policies/` and they're automatically loaded — no `--custom` flag or config changes needed. Works like git hooks: drop a file, it just works.
224
+
225
+ ```text
226
+ # Project level — committed to git, shared with the team
227
+ .failproofai/policies/security-policies.mjs
228
+ .failproofai/policies/workflow-policies.mjs
229
+
230
+ # User level — personal, applies to all projects
231
+ ~/.failproofai/policies/my-policies.mjs
232
+ ```
233
+
234
+ Both levels load (union). Files are loaded alphabetically within each directory. Prefix with `01-`, `02-`, etc. to control order. See [examples/convention-policies/](examples/convention-policies/) for ready-to-use examples.
235
+
219
236
  ---
220
237
 
221
238
  ## Telemetry
@@ -97,6 +97,11 @@ COMMANDS
97
97
  --version, -v Print version and exit
98
98
  --help, -h Show this help message
99
99
 
100
+ CONVENTION POLICIES
101
+ Drop *policies.{js,mjs,ts} files into .failproofai/policies/ for auto-loading.
102
+ Works at project level (.failproofai/policies/) and user level (~/.failproofai/policies/).
103
+ No --custom flag or config changes needed — just drop files and they're picked up.
104
+
100
105
  EXAMPLES
101
106
  failproofai policies
102
107
  failproofai policies --install