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