failproofai 1.0.8 → 1.0.9-beta.0

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 (160) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  30. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +15 -15
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  41. package/.next/standalone/.next/server/app/policies/page.js +3 -3
  42. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  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/react-loadable-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  55. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
  59. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  60. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0tmz862._.js +3 -0
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  73. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
  74. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/{_0-vcssj._.js → _0n0lja6._.js} +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/_1p3iz2d._.js +23 -0
  80. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +3 -3
  82. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +1 -1
  89. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  90. package/.next/standalone/.next/server/pages/404.html +1 -1
  91. package/.next/standalone/.next/server/pages/500.html +1 -1
  92. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  93. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  94. package/.next/standalone/.next/static/chunks/{3c3qbmosdjl6w.js → 0247hc9ath5t-.js} +1 -1
  95. package/.next/standalone/.next/static/chunks/{0m-9d6yn9hx4j.js → 03001k9jy-kst.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{2qdpj67x6ifk_.js → 06k7gsfnb7jkh.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/0qejem4-zt-hy.js +1 -0
  98. package/.next/standalone/.next/static/chunks/{21uv-uusw329x.js → 0snylg3y1i47a.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/1h0if8ky362as.js +1 -0
  100. package/.next/standalone/.next/static/chunks/{0wbjy0zo-m7is.js → 2qsrrrl2maqsb.js} +1 -1
  101. package/.next/standalone/.next/static/chunks/{0bhidk90e-07f.css → 3_e_wg5_oay87.css} +1 -1
  102. package/.next/standalone/.next/static/chunks/3ns-egfh8i8i5.js +6 -0
  103. package/.next/standalone/.next/static/chunks/3rq7fjj9we0v3.js +1 -0
  104. package/.next/standalone/.next/static/chunks/42m0f2hh8vfb1.js +1 -0
  105. package/.next/standalone/app/actions/get-jev-config.ts +2 -2
  106. package/.next/standalone/app/actions/update-jev-config.ts +12 -9
  107. package/.next/standalone/app/components/jev-notices.tsx +10 -10
  108. package/.next/standalone/app/settings/jev-panel.tsx +11 -11
  109. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +2 -0
  110. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +26 -0
  111. package/.next/standalone/fp-cloud-cli/tests/test_output.py +27 -0
  112. package/.next/standalone/hermes-plugin/README.md +18 -8
  113. package/.next/standalone/package.json +10 -10
  114. package/.next/standalone/server.js +1 -1
  115. package/bin/failproofai.mjs +33 -7
  116. package/dist/cli.mjs +556 -521
  117. package/dist/worker.mjs +56 -375
  118. package/hermes-plugin/README.md +18 -8
  119. package/package.json +10 -10
  120. package/scripts/build-policy-pack.mjs +6 -9
  121. package/src/hooks/cloud-connection.ts +12 -6
  122. package/src/hooks/custom-hooks-loader.ts +1 -1
  123. package/src/hooks/effective-reviewers.ts +50 -43
  124. package/src/hooks/handler.ts +25 -16
  125. package/src/hooks/hermes-update.ts +74 -0
  126. package/src/hooks/hook-activity-store.ts +3 -3
  127. package/src/hooks/integrations.ts +349 -55
  128. package/src/hooks/jev-activity.ts +3 -3
  129. package/src/hooks/jev-cli.ts +17 -17
  130. package/src/hooks/jev-cloud-connection.ts +4 -4
  131. package/src/hooks/manager.ts +3 -2
  132. package/src/hooks/pack-cli.ts +24 -23
  133. package/src/hooks/pack-manifest.ts +25 -2
  134. package/src/hooks/pack-store.ts +2 -2
  135. package/src/hooks/policy-authority.ts +51 -32
  136. package/src/hooks/policy-evaluator.ts +5 -5
  137. package/src/hooks/policy-registry.ts +5 -7
  138. package/src/hooks/policy-reviewability.ts +26 -10
  139. package/src/hooks/policy-types.ts +4 -4
  140. package/src/hooks/semantic/combine.ts +13 -13
  141. package/src/hooks/semantic/evaluator.ts +5 -5
  142. package/src/hooks/semantic/jev-client.ts +1 -1
  143. package/src/hooks/semantic/jev-config.ts +21 -12
  144. package/src/hooks/semantic/jev-review.ts +3 -3
  145. package/src/hooks/semantic/jev-stats.ts +17 -17
  146. package/src/hooks/semantic/pack-policies.ts +48 -48
  147. package/src/hooks/semantic/policies.ts +13 -425
  148. package/src/hooks/semantic/precondition-names.ts +1 -1
  149. package/src/hooks/semantic/preconditions.ts +1 -1
  150. package/src/hooks/semantic/types.ts +1 -1
  151. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13t1zkw._.js +0 -3
  152. package/.next/standalone/.next/server/chunks/ssr/_13kfn90._.js +0 -23
  153. package/.next/standalone/.next/static/chunks/06dnzbolj00mc.js +0 -6
  154. package/.next/standalone/.next/static/chunks/16f3fa-lx38hk.js +0 -1
  155. package/.next/standalone/.next/static/chunks/2g2tki08kdhie.js +0 -1
  156. package/.next/standalone/.next/static/chunks/3-nbtkhg9y-1j.js +0 -1
  157. package/.next/standalone/.next/static/chunks/355km0ihuqo1p.js +0 -1
  158. /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → z1C0kJVF0NCtWn5R88hW3}/_buildManifest.js +0 -0
  159. /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → z1C0kJVF0NCtWn5R88hW3}/_clientMiddlewareManifest.js +0 -0
  160. /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → z1C0kJVF0NCtWn5R88hW3}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -29,7 +29,7 @@ var __esm = (fn, res, err) => () => {
29
29
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
30
 
31
31
  // package.json
32
- var version2 = "1.0.8", package_default;
32
+ var version2 = "1.0.9-beta.0", package_default;
33
33
  var init_package = __esm(() => {
34
34
  package_default = {
35
35
  name: "failproofai",
@@ -149,22 +149,22 @@ var init_package = __esm(() => {
149
149
  postcss: "8.5.26",
150
150
  "eslint-plugin-react-hooks": "7.0.1",
151
151
  vite: "8.0.16",
152
- undici: "7.29.0",
152
+ undici: "7.29.1",
153
153
  "brace-expansion": "5.0.9",
154
154
  sharp: "0.35.4",
155
155
  browserslist: "4.28.8"
156
156
  },
157
157
  optionalDependencies: {
158
- "@failproofai/failproofaid-linux-x64": "1.0.8",
159
- "@failproofai/failproofaid-linux-arm64": "1.0.8",
160
- "@failproofai/failproofaid-darwin-x64": "1.0.8",
161
- "@failproofai/failproofaid-darwin-arm64": "1.0.8"
158
+ "@failproofai/failproofaid-linux-x64": "1.0.9-beta.0",
159
+ "@failproofai/failproofaid-linux-arm64": "1.0.9-beta.0",
160
+ "@failproofai/failproofaid-darwin-x64": "1.0.9-beta.0",
161
+ "@failproofai/failproofaid-darwin-arm64": "1.0.9-beta.0"
162
162
  },
163
163
  failproofaidBinaries: {
164
- "linux-x64": "35b641f60e1724513fbb760db382123dad2b5fcdd67dda6373bda80651bd36bb",
165
- "linux-arm64": "a058288f5d08f99612f053793e5c7f59264d3da97f3dbe769e401b0f8536702d",
166
- "darwin-x64": "b4f3dbe4aea44e77cc10839647c0dc03a1b8b797dfb27e1d89a6a96554ef27b8",
167
- "darwin-arm64": "fd42f28bf92001d39117b488a215578b966f07d316214744aca64a1888f2aed2"
164
+ "linux-x64": "044b6619007227b8aa1fc1095b8c61a17bd90e11da53183ac1f5adecdcae6839",
165
+ "linux-arm64": "479450d7d15c52a59bd1a8cf4c7086be9210e8d31ad4f98d99b123f571e944bd",
166
+ "darwin-x64": "11562912ff726195bfb966ed24a774ffea38d01acc3edc1f9bfa3ea9c298977f",
167
+ "darwin-arm64": "5bbaee1ec89d2d8032b7f16e502fb96891fbc6aa2ee6c861f43a64a0a60e61ba"
168
168
  }
169
169
  };
170
170
  });
@@ -875,7 +875,7 @@ var init_jev_activity = __esm(() => {
875
875
  ];
876
876
  EVALUATORS = new Set(["jev", "jev-fallback"]);
877
877
  DECISIONS = new Set(["allow", "instruct", "deny"]);
878
- MODES = new Set(["shadow", "enforce"]);
878
+ MODES = new Set(["observe", "enforce"]);
879
879
  REASON_HEAD_RE = /^([a-z0-9]+(?:-[a-z0-9]+)*)\s*(?:$|[:(])/;
880
880
  HTTP_CODE_RE = /^http-\d{3}$/;
881
881
  JEV_REASON_CODE_LIST = [
@@ -2518,7 +2518,7 @@ function effectiveAuthority(p) {
2518
2518
 
2519
2519
  // src/hooks/policy-authority.ts
2520
2520
  import { existsSync as existsSync4 } from "node:fs";
2521
- function resolvePolicyAuthority(decl, knownReviewers = SEMANTIC_REVIEWER_NAMES, contested) {
2521
+ function resolvePolicyAuthority(decl, knownReviewers = NO_REVIEWERS, contested) {
2522
2522
  const declaredReviewable = decl?.authority === "reviewable";
2523
2523
  if (!decl || effectiveAuthority(decl) === "hard") {
2524
2524
  if (!declaredReviewable)
@@ -2530,6 +2530,8 @@ function resolvePolicyAuthority(decl, knownReviewers = SEMANTIC_REVIEWER_NAMES,
2530
2530
  return { authority: "hard", downgraded: "reviewedBy is not a list of semantic policy names" };
2531
2531
  }
2532
2532
  const unknown = names.filter((n) => !knownReviewers.has(n));
2533
+ if (knownReviewers.size === 0 && !unknown.some((n) => contested?.has(n)))
2534
+ return { authority: "hard" };
2533
2535
  if (unknown.length > 0) {
2534
2536
  return { authority: "hard", downgraded: `reviewedBy names ${unknown.map((n) => whyUnknown(n, knownReviewers, contested)).join("; ")}` };
2535
2537
  }
@@ -2575,7 +2577,7 @@ function authorityProblem(decl, knownReviewers = SEMANTIC_REVIEWER_NAMES) {
2575
2577
  const { downgraded } = resolvePolicyAuthority(decl, knownReviewers);
2576
2578
  return downgraded ? `authority "reviewable" was refused — ${downgraded}` : undefined;
2577
2579
  }
2578
- function withMergedAuthority(record, declarations, knownReviewers = SEMANTIC_REVIEWER_NAMES) {
2580
+ function withMergedAuthority(record, declarations, knownReviewers = NO_REVIEWERS) {
2579
2581
  const resolved = declarations.map((d) => resolvePolicyAuthority(d, knownReviewers));
2580
2582
  const allReviewable = resolved.length > 0 && resolved.every((r) => r.authority === "reviewable");
2581
2583
  const decided = declarations[resolved.findIndex((r) => r.authority !== "reviewable")] ?? {};
@@ -2614,7 +2616,7 @@ function authorityDeclarationFor(hook, source) {
2614
2616
  }
2615
2617
  return { authority: hook.authority, reviewedBy: hook.reviewedBy };
2616
2618
  }
2617
- var SEMANTIC_POLICY_NAMES, SEMANTIC_REVIEWER_NAMES, isName = (n) => typeof n === "string" && n.length > 0, warnedAuthority;
2619
+ var SEMANTIC_POLICY_NAMES, SEMANTIC_REVIEWER_NAMES, NO_REVIEWERS, isName = (n) => typeof n === "string" && n.length > 0, warnedAuthority;
2618
2620
  var init_policy_authority = __esm(() => {
2619
2621
  init_fp_home();
2620
2622
  init_hook_logger();
@@ -2637,6 +2639,7 @@ var init_policy_authority = __esm(() => {
2637
2639
  "external-data-egress"
2638
2640
  ];
2639
2641
  SEMANTIC_REVIEWER_NAMES = new Set(SEMANTIC_POLICY_NAMES);
2642
+ NO_REVIEWERS = new Set;
2640
2643
  warnedAuthority = new Set;
2641
2644
  });
2642
2645
 
@@ -2849,6 +2852,16 @@ function hasInstalledPacks() {
2849
2852
  return false;
2850
2853
  }
2851
2854
  }
2855
+ function hasInstalledRegexPacks() {
2856
+ try {
2857
+ const raw = JSON.parse(readFileSync4(installedFilePath(), "utf8"));
2858
+ if (!Array.isArray(raw.packs))
2859
+ return false;
2860
+ return raw.packs.some((p) => !!p && typeof p === "object" && Array.isArray(p.policies) && p.policies.length > 0);
2861
+ } catch {
2862
+ return false;
2863
+ }
2864
+ }
2852
2865
  function installedFilePath() {
2853
2866
  return process.env.FAILPROOFAI_PACK_DIR ? resolve4(process.env.FAILPROOFAI_PACK_DIR, "installed.json") : packsInstalledFile();
2854
2867
  }
@@ -3267,7 +3280,7 @@ function isReservedClaim(pack, name) {
3267
3280
  function effectiveReviewerNames() {
3268
3281
  if (cached)
3269
3282
  return cached;
3270
- let names = SEMANTIC_REVIEWER_NAMES;
3283
+ let names = NO_REVIEWERS;
3271
3284
  cachedContested = new Map;
3272
3285
  try {
3273
3286
  const packs = jevPacks(readInstalledPacks().packs, reviewerCli);
@@ -3281,15 +3294,22 @@ function contestedReviewerNames() {
3281
3294
  effectiveReviewerNames();
3282
3295
  return cachedContested;
3283
3296
  }
3284
- function replacesBuiltinChecks(packs) {
3285
- return packs.some((p) => isFirstPartyPack(p) && (p.semantic ?? []).length > 0);
3297
+ function jevChecksInstalled() {
3298
+ return effectiveReviewerNames().size > 0;
3299
+ }
3300
+ function jevChecksDeclared() {
3301
+ try {
3302
+ return reviewerNamesFor(jevPacks(readInstalledPacks().packs)).size > 0;
3303
+ } catch {
3304
+ return false;
3305
+ }
3286
3306
  }
3287
3307
  function reviewerNamesFor(packs) {
3288
3308
  const contested = contestedSemanticNames(packs);
3289
3309
  const declared = packs.flatMap((p) => (p.semantic ?? []).filter((s) => !isReservedClaim(p, s.name)).map((s) => s.name)).filter((name) => !contested.has(name));
3290
3310
  if (declared.length === 0)
3291
- return SEMANTIC_REVIEWER_NAMES;
3292
- return new Set(replacesBuiltinChecks(packs) ? declared : [...SEMANTIC_REVIEWER_NAMES, ...declared]);
3311
+ return NO_REVIEWERS;
3312
+ return new Set(declared);
3293
3313
  }
3294
3314
  function forgetEffectiveReviewerNames(cli) {
3295
3315
  cached = null;
@@ -12610,11 +12630,14 @@ import {
12610
12630
  existsSync as existsSync9,
12611
12631
  lstatSync,
12612
12632
  mkdirSync as mkdirSync4,
12633
+ readlinkSync,
12634
+ realpathSync as realpathSync2,
12613
12635
  renameSync as renameSync2,
12614
12636
  rmSync as rmSync2,
12637
+ symlinkSync,
12615
12638
  unlinkSync
12616
12639
  } from "node:fs";
12617
- import { resolve as resolve8, dirname as dirname5 } from "node:path";
12640
+ import { resolve as resolve8, dirname as dirname5, basename as basename3 } from "node:path";
12618
12641
  import { fileURLToPath } from "node:url";
12619
12642
  import { homedir as homedir6 } from "node:os";
12620
12643
  function readJsonFile(path) {
@@ -12918,33 +12941,84 @@ function makePiProjectRelativeEntry(extPath) {
12918
12941
  function getHermesPluginSourcePath() {
12919
12942
  const fromEnv = process.env.FAILPROOFAI_PACKAGE_ROOT;
12920
12943
  if (fromEnv)
12921
- return resolve8(fromEnv, "hermes-plugin");
12922
- return resolve8(fileURLToPath(import.meta.url), "..", "..", "..", "hermes-plugin");
12944
+ return resolve8(fromEnv, HERMES_PLUGIN_SOURCE_DIR);
12945
+ return resolve8(fileURLToPath(import.meta.url), "..", "..", "..", HERMES_PLUGIN_SOURCE_DIR);
12923
12946
  }
12924
12947
  function hermesPluginPathForSettings(settingsPath) {
12925
12948
  return resolve8(dirname5(settingsPath), "plugins", HERMES_PLUGIN_ID);
12926
12949
  }
12927
- function hasManagedHermesPlugin(settingsPath) {
12928
- const pluginPath = hermesPluginPathForSettings(settingsPath);
12950
+ function pathPresent(path) {
12929
12951
  try {
12930
- if (lstatSync(pluginPath).isSymbolicLink())
12931
- return false;
12932
- if (!existsSync9(resolve8(pluginPath, HERMES_PLUGIN_MARKER)))
12933
- return false;
12934
- return HERMES_PLUGIN_FILES.every((file) => existsSync9(resolve8(pluginPath, file)));
12952
+ lstatSync(path);
12953
+ return true;
12935
12954
  } catch {
12936
12955
  return false;
12937
12956
  }
12938
12957
  }
12939
- function hasHermesPluginMarker(settingsPath) {
12940
- const pluginPath = hermesPluginPathForSettings(settingsPath);
12958
+ function hasAllHermesPluginFiles(dir) {
12959
+ return HERMES_PLUGIN_FILES.every((file) => existsSync9(resolve8(dir, file)));
12960
+ }
12961
+ function isFailproofaiHermesPluginSource(dir) {
12962
+ if (basename3(dir) !== HERMES_PLUGIN_SOURCE_DIR)
12963
+ return false;
12964
+ if (!existsSync9(dir))
12965
+ return true;
12941
12966
  try {
12942
- return !lstatSync(pluginPath).isSymbolicLink() && existsSync9(resolve8(pluginPath, HERMES_PLUGIN_MARKER));
12967
+ const manifest = readFileSync5(resolve8(dir, "plugin.yaml"), "utf8");
12968
+ return /^name:\s*["']?failproofai["']?\s*$/m.test(manifest);
12943
12969
  } catch {
12944
12970
  return false;
12945
12971
  }
12946
12972
  }
12947
- function installHermesPlugin(settingsPath) {
12973
+ function samePath(a, b) {
12974
+ if (resolve8(a) === resolve8(b))
12975
+ return true;
12976
+ try {
12977
+ return realpathSync2(a) === realpathSync2(b);
12978
+ } catch {
12979
+ return false;
12980
+ }
12981
+ }
12982
+ function hermesPluginState(settingsPath) {
12983
+ const pluginPath = hermesPluginPathForSettings(settingsPath);
12984
+ let isLink;
12985
+ let isDirectory;
12986
+ try {
12987
+ const stat = lstatSync(pluginPath);
12988
+ isLink = stat.isSymbolicLink();
12989
+ isDirectory = stat.isDirectory();
12990
+ } catch {
12991
+ return { kind: "absent" };
12992
+ }
12993
+ if (isLink) {
12994
+ let target;
12995
+ try {
12996
+ target = resolve8(dirname5(pluginPath), readlinkSync(pluginPath));
12997
+ } catch {
12998
+ return { kind: "foreign" };
12999
+ }
13000
+ if (!isFailproofaiHermesPluginSource(target))
13001
+ return { kind: "foreign" };
13002
+ return {
13003
+ kind: "link",
13004
+ target,
13005
+ complete: hasAllHermesPluginFiles(target),
13006
+ current: samePath(target, getHermesPluginSourcePath())
13007
+ };
13008
+ }
13009
+ if (isDirectory && existsSync9(resolve8(pluginPath, HERMES_PLUGIN_MARKER))) {
13010
+ return { kind: "copy", complete: hasAllHermesPluginFiles(pluginPath) };
13011
+ }
13012
+ return { kind: "foreign" };
13013
+ }
13014
+ function hasManagedHermesPlugin(settingsPath) {
13015
+ const state = hermesPluginState(settingsPath);
13016
+ return (state.kind === "link" || state.kind === "copy") && state.complete;
13017
+ }
13018
+ function defaultHermesSymlink(target, path) {
13019
+ symlinkSync(target, path, process.platform === "win32" ? "junction" : "dir");
13020
+ }
13021
+ function installHermesPlugin(settingsPath, deps = {}) {
12948
13022
  const source = getHermesPluginSourcePath();
12949
13023
  for (const file of HERMES_PLUGIN_FILES) {
12950
13024
  if (!existsSync9(resolve8(source, file))) {
@@ -12952,23 +13026,47 @@ function installHermesPlugin(settingsPath) {
12952
13026
  }
12953
13027
  }
12954
13028
  const destination = hermesPluginPathForSettings(settingsPath);
12955
- if (existsSync9(destination)) {
12956
- if (lstatSync(destination).isSymbolicLink() || !existsSync9(resolve8(destination, HERMES_PLUGIN_MARKER))) {
12957
- throw new Error(`Refusing to overwrite an unmanaged Hermes plugin at ${destination}. ` + `Move it aside or install FailproofAI under a different Hermes profile.`);
12958
- }
13029
+ const existing = hermesPluginState(settingsPath);
13030
+ if (existing.kind === "foreign") {
13031
+ throw new Error(`Refusing to overwrite an unmanaged Hermes plugin at ${destination}. ` + `Move it aside or install FailproofAI under a different Hermes profile.`);
12959
13032
  }
13033
+ if (existing.kind === "link" && existing.current)
13034
+ return "unchanged";
12960
13035
  mkdirSync4(dirname5(destination), { recursive: true });
12961
13036
  const suffix = `${process.pid}-${Date.now()}`;
12962
13037
  const temporary = `${destination}.install-${suffix}`;
12963
- const backup = `${destination}.backup-${suffix}`;
12964
- let movedExisting = false;
13038
+ let linked = false;
12965
13039
  try {
13040
+ (deps.symlink ?? defaultHermesSymlink)(source, temporary);
13041
+ linked = true;
13042
+ } catch {
13043
+ rmSync2(temporary, { recursive: true, force: true });
13044
+ }
13045
+ if (!linked) {
12966
13046
  mkdirSync4(temporary, { recursive: true });
12967
- for (const file of HERMES_PLUGIN_FILES) {
12968
- cpSync(resolve8(source, file), resolve8(temporary, file));
13047
+ try {
13048
+ for (const file of HERMES_PLUGIN_FILES) {
13049
+ cpSync(resolve8(source, file), resolve8(temporary, file));
13050
+ }
13051
+ writeFileSync3(resolve8(temporary, HERMES_PLUGIN_MARKER), "Managed by failproofai. Remove with `failproofai policies --uninstall --cli hermes`.\n", { encoding: "utf8", mode: 384 });
13052
+ } catch (err) {
13053
+ rmSync2(temporary, { recursive: true, force: true });
13054
+ throw err;
13055
+ }
13056
+ }
13057
+ if (linked && (existing.kind === "absent" || existing.kind === "link")) {
13058
+ try {
13059
+ renameSync2(temporary, destination);
13060
+ } catch (err) {
13061
+ rmSync2(temporary, { force: true });
13062
+ throw err;
12969
13063
  }
12970
- writeFileSync3(resolve8(temporary, HERMES_PLUGIN_MARKER), "Managed by failproofai. Remove with `failproofai policies --uninstall --cli hermes`.\n", { encoding: "utf8", mode: 384 });
12971
- if (existsSync9(destination)) {
13064
+ return "linked";
13065
+ }
13066
+ const backup = `${destination}.backup-${suffix}`;
13067
+ let movedExisting = false;
13068
+ try {
13069
+ if (pathPresent(destination)) {
12972
13070
  renameSync2(destination, backup);
12973
13071
  movedExisting = true;
12974
13072
  }
@@ -12977,15 +13075,21 @@ function installHermesPlugin(settingsPath) {
12977
13075
  rmSync2(backup, { recursive: true, force: true });
12978
13076
  } catch (err) {
12979
13077
  rmSync2(temporary, { recursive: true, force: true });
12980
- if (movedExisting && !existsSync9(destination) && existsSync9(backup)) {
13078
+ if (movedExisting && !pathPresent(destination) && pathPresent(backup)) {
12981
13079
  renameSync2(backup, destination);
12982
13080
  }
12983
13081
  throw err;
12984
13082
  }
13083
+ return linked ? "linked" : "copied";
12985
13084
  }
12986
13085
  function removeManagedHermesPlugin(settingsPath) {
12987
13086
  const pluginPath = hermesPluginPathForSettings(settingsPath);
12988
- if (!hasHermesPluginMarker(settingsPath))
13087
+ const state = hermesPluginState(settingsPath);
13088
+ if (state.kind === "link") {
13089
+ unlinkSync(pluginPath);
13090
+ return true;
13091
+ }
13092
+ if (state.kind !== "copy")
12989
13093
  return false;
12990
13094
  rmSync2(pluginPath, { recursive: true, force: true });
12991
13095
  return true;
@@ -13030,6 +13134,93 @@ function hermesConfigState(settingsPath) {
13030
13134
  legacyShellHookPresent
13031
13135
  };
13032
13136
  }
13137
+ function enableHermesPluginInDoc(doc) {
13138
+ removeLegacyHermesHooks(doc);
13139
+ const js = doc.toJS() ?? {};
13140
+ const plugins = js.plugins && typeof js.plugins === "object" ? js.plugins : {};
13141
+ const enabled = Array.isArray(plugins.enabled) ? [...plugins.enabled] : [];
13142
+ if (!enabled.includes(HERMES_PLUGIN_ID))
13143
+ enabled.push(HERMES_PLUGIN_ID);
13144
+ plugins.enabled = enabled;
13145
+ if (Array.isArray(plugins.disabled)) {
13146
+ plugins.disabled = plugins.disabled.filter((name) => name !== HERMES_PLUGIN_ID);
13147
+ if (plugins.disabled.length === 0)
13148
+ delete plugins.disabled;
13149
+ }
13150
+ doc.set("plugins", plugins);
13151
+ }
13152
+ async function migrateHermesProfiles(opts) {
13153
+ const results = [];
13154
+ let daemonOk;
13155
+ for (const profile of listHermesProfiles()) {
13156
+ if (!existsSync9(profile.home))
13157
+ continue;
13158
+ const settingsPath = resolve8(profile.home, "config.yaml");
13159
+ const pluginPath = hermesPluginPathForSettings(settingsPath);
13160
+ const config = hermesConfigState(settingsPath);
13161
+ const plugin = hermesPluginState(settingsPath);
13162
+ const ours = plugin.kind === "link" || plugin.kind === "copy";
13163
+ const base = { name: profile.name, home: profile.home };
13164
+ const legacy = config.legacyShellHookPresent;
13165
+ if (!legacy && !ours && !config.pluginEnabled) {
13166
+ results.push({ ...base, status: "untouched", detail: "no failproofai integration", legacyShellHooksRemain: false });
13167
+ continue;
13168
+ }
13169
+ if (plugin.kind === "link" && plugin.current && plugin.complete && config.pluginEnabled && !legacy) {
13170
+ results.push({ ...base, status: "current", detail: "already linked", legacyShellHooksRemain: false });
13171
+ continue;
13172
+ }
13173
+ if (plugin.kind === "foreign") {
13174
+ results.push({
13175
+ ...base,
13176
+ status: "failed",
13177
+ detail: `an unmanaged plugin occupies ${pluginPath}` + (legacy ? "; shell hooks left in place (cron jobs are not checked)" : ""),
13178
+ legacyShellHooksRemain: legacy
13179
+ });
13180
+ continue;
13181
+ }
13182
+ if (existsSync9(settingsPath)) {
13183
+ const errors = $parseDocument(readFileSync5(settingsPath, "utf8")).errors;
13184
+ if (errors.length > 0) {
13185
+ results.push({
13186
+ ...base,
13187
+ status: "failed",
13188
+ detail: `${settingsPath} does not parse as YAML; left untouched`,
13189
+ legacyShellHooksRemain: legacy
13190
+ });
13191
+ continue;
13192
+ }
13193
+ }
13194
+ if (daemonOk === undefined)
13195
+ daemonOk = await opts.daemonSupportsPolicyEvaluation();
13196
+ if (!daemonOk) {
13197
+ results.push({
13198
+ ...base,
13199
+ status: "blocked",
13200
+ detail: legacy ? "daemon lacks native policy evaluation; shell hooks left in place (cron jobs are not checked)" : "daemon lacks native policy evaluation; the plugin here cannot get verdicts",
13201
+ legacyShellHooksRemain: legacy
13202
+ });
13203
+ continue;
13204
+ }
13205
+ try {
13206
+ const mode = installHermesPlugin(settingsPath, opts.installDeps);
13207
+ const doc = readYamlDoc(settingsPath);
13208
+ enableHermesPluginInDoc(doc);
13209
+ writeYamlDoc(settingsPath, doc);
13210
+ const from = legacy ? "shell hooks" : plugin.kind === "copy" ? "copied plugin" : plugin.kind === "link" ? "old plugin link" : "enabled but missing plugin";
13211
+ const to = mode === "copied" ? "plugin copy (symlink not possible here)" : "linked plugin";
13212
+ results.push({ ...base, status: "migrated", detail: `${from} → ${to}`, legacyShellHooksRemain: false });
13213
+ } catch (err) {
13214
+ results.push({
13215
+ ...base,
13216
+ status: "failed",
13217
+ detail: err instanceof Error ? err.message : String(err),
13218
+ legacyShellHooksRemain: hermesConfigState(settingsPath).legacyShellHookPresent
13219
+ });
13220
+ }
13221
+ }
13222
+ return results;
13223
+ }
13033
13224
  function hermesConfigHasHooks(settingsPath) {
13034
13225
  try {
13035
13226
  const state = hermesConfigState(settingsPath);
@@ -13067,7 +13258,7 @@ function listInstallableIds() {
13067
13258
  function detectInstalledClis() {
13068
13259
  return INTEGRATION_TYPES.filter((id) => INTEGRATIONS[id]?.detectInstalled());
13069
13260
  }
13070
- var claudeCode, codex, copilot, cursor, OPENCODE_PLUGIN_REL_PATH = "./plugins/failproofai.mjs", opencode, pi, HERMES_PLUGIN_ID = "failproofai", HERMES_PLUGIN_MARKER = ".failproofai-managed", HERMES_PLUGIN_FILES, hermes, OPENCLAW_PLUGIN_ID = "failproofai", openclaw, factory, devin, ANTIGRAVITY_HOOK_NAME = "failproofai", ANTIGRAVITY_TOOL_EVENTS, antigravity, goose, INTEGRATIONS;
13261
+ var claudeCode, codex, copilot, cursor, OPENCODE_PLUGIN_REL_PATH = "./plugins/failproofai.mjs", opencode, pi, HERMES_PLUGIN_ID = "failproofai", HERMES_PLUGIN_MARKER = ".failproofai-managed", HERMES_PLUGIN_FILES, HERMES_PLUGIN_SOURCE_DIR = "hermes-plugin", hermes, OPENCLAW_PLUGIN_ID = "failproofai", openclaw, factory, devin, ANTIGRAVITY_HOOK_NAME = "failproofai", ANTIGRAVITY_TOOL_EVENTS, antigravity, goose, INTEGRATIONS;
13071
13262
  var init_integrations = __esm(() => {
13072
13263
  init_dist();
13073
13264
  init_hermes_profiles();
@@ -13750,20 +13941,7 @@ var init_integrations = __esm(() => {
13750
13941
  },
13751
13942
  isFailproofaiHook: isMarkedHook,
13752
13943
  writeHookEntries(settings) {
13753
- const doc = settings;
13754
- removeLegacyHermesHooks(doc);
13755
- const js = doc.toJS() ?? {};
13756
- const plugins = js.plugins && typeof js.plugins === "object" ? js.plugins : {};
13757
- const enabled = Array.isArray(plugins.enabled) ? [...plugins.enabled] : [];
13758
- if (!enabled.includes(HERMES_PLUGIN_ID))
13759
- enabled.push(HERMES_PLUGIN_ID);
13760
- plugins.enabled = enabled;
13761
- if (Array.isArray(plugins.disabled)) {
13762
- plugins.disabled = plugins.disabled.filter((name) => name !== HERMES_PLUGIN_ID);
13763
- if (plugins.disabled.length === 0)
13764
- delete plugins.disabled;
13765
- }
13766
- doc.set("plugins", plugins);
13944
+ enableHermesPluginInDoc(settings);
13767
13945
  },
13768
13946
  removeHooksFromFile(settingsPath) {
13769
13947
  const doc = readYamlDoc(settingsPath);
@@ -14386,7 +14564,7 @@ function outsideProject(facts, p) {
14386
14564
  return false;
14387
14565
  return p.resolved !== cwd && !p.resolved.startsWith(cwd.endsWith("/") ? cwd : cwd + "/");
14388
14566
  }
14389
- var PROTECTED_BRANCHES, OUTSIDE_PROJECT, SEMANTIC_POLICIES, SCOPE_PROBE, INJECTION_PROBE, HUMAN_ONLY, TASK_STEP_PROBE, OP_REQUESTED_PROBE, BEYOND_TASK_PROBE, TASK_PROBES;
14567
+ var PROTECTED_BRANCHES, OUTSIDE_PROJECT, SCOPE_PROBE, INJECTION_PROBE, HUMAN_ONLY, TASK_STEP_PROBE, OP_REQUESTED_PROBE, BEYOND_TASK_PROBE, TASK_PROBES;
14390
14568
  var init_policies = __esm(() => {
14391
14569
  PROTECTED_BRANCHES = new Set(["main", "master", "production", "prod", "release", "trunk"]);
14392
14570
  OUTSIDE_PROJECT = new Set([
@@ -14395,334 +14573,6 @@ var init_policies = __esm(() => {
14395
14573
  "system",
14396
14574
  "root"
14397
14575
  ]);
14398
- SEMANTIC_POLICIES = [
14399
- {
14400
- name: "destructive-deletion",
14401
- title: "Tried to permanently delete data that cannot be regenerated",
14402
- appliesTo: ["shell", "write"],
14403
- mode: "deny",
14404
- userCanOverride: true,
14405
- probes: [
14406
- {
14407
- id: "destroys",
14408
- instructions: "The tool call in `agent_request` permanently deletes, wipes, truncates or overwrites existing files, " + "directories or disks — for example `rm -r`, `find -delete`, `shred`, `dd of=`, `mkfs`, `truncate`, " + "`rsync --delete`, `git clean -fdx`, or redirecting output over an existing file.",
14409
- criteria: {
14410
- true: "Existing data would be destroyed or irreversibly overwritten.",
14411
- false: "Nothing existing is destroyed: it reads, lists, creates new files, or moves data somewhere recoverable."
14412
- }
14413
- },
14414
- {
14415
- id: "irreplaceable",
14416
- instructions: "What would be destroyed is outside the project, is the whole project, is the user's home directory, " + "is the filesystem root, or is data that cannot simply be regenerated (source code, documents, databases, " + "keys). Use `facts.paths[].relation` to see where each target sits relative to the project.",
14417
- criteria: {
14418
- true: "The destroyed data matters and could not be rebuilt by running a command.",
14419
- false: "Only regenerable data inside the project is affected: build output, dist/, caches, node_modules, " + "virtualenvs, coverage reports, temp files, or files the agent itself just created."
14420
- }
14421
- }
14422
- ],
14423
- guidance: "This permanently deletes data that cannot be rebuilt. Confirm the exact paths with the user first."
14424
- },
14425
- {
14426
- name: "production-infra-change",
14427
- title: "Tried to change live infrastructure",
14428
- appliesTo: ["shell"],
14429
- mode: "deny",
14430
- userCanOverride: true,
14431
- probes: [
14432
- {
14433
- id: "mutates",
14434
- instructions: "The command in `agent_request` changes the state of cloud or cluster infrastructure: it creates, " + "updates, deletes, applies, scales, restarts, rolls out, deploys or destroys resources in a cloud " + "account, Kubernetes cluster, managed database, DNS, CDN or hosting platform (any CLI: kubectl, helm, " + "terraform, tofu, pulumi, aws, gcloud, az, doctl, flyctl, vercel, wrangler, railway, and so on — " + "however the binary is spelled or pathed).",
14435
- criteria: {
14436
- true: "It mutates infrastructure.",
14437
- false: "It only reads or plans: get, list, describe, logs, status, plan, diff, validate, whoami, or --dry-run."
14438
- }
14439
- },
14440
- {
14441
- id: "not_local",
14442
- instructions: "The target of that change is a shared or production environment, or its environment cannot be told " + "from the command.",
14443
- criteria: {
14444
- true: "Production, shared, or unknown environment.",
14445
- false: "Clearly a local or throwaway environment: localhost, kind, minikube, docker-desktop, k3d, or a " + "context, workspace or profile whose name says dev, test, staging, sandbox or local."
14446
- }
14447
- }
14448
- ],
14449
- guidance: "This changes live infrastructure. Run it against a non-production target, or hand the command to a human."
14450
- },
14451
- {
14452
- name: "git-history-rewrite",
14453
- title: "Tried to rewrite or discard shared git history",
14454
- appliesTo: ["shell"],
14455
- mode: "deny",
14456
- userCanOverride: true,
14457
- probes: [
14458
- {
14459
- id: "rewrites_remote",
14460
- instructions: "The command in `agent_request` force-pushes or otherwise overwrites history on a git remote: " + "`git push --force`, `--force-with-lease`, `-f`, a `+refspec` such as `+HEAD:main`, or deleting a " + "remote branch. Git's global options before the subcommand (`git -C dir`, `git -c key=val`) and an " + "absolute path to the git binary do not change what the command does.",
14461
- criteria: {
14462
- true: "Remote history would be overwritten or a remote branch deleted.",
14463
- false: "A normal push that only adds commits, or no push at all."
14464
- }
14465
- }
14466
- ],
14467
- guidance: "Force-pushing rewrites history other people may have pulled. Confirm with the user first."
14468
- },
14469
- {
14470
- name: "push-to-protected-branch",
14471
- title: "Tried to push directly to a protected branch",
14472
- appliesTo: ["shell"],
14473
- mode: "instruct",
14474
- userCanOverride: true,
14475
- probes: [
14476
- {
14477
- id: "pushes_protected",
14478
- instructions: "The command in `agent_request` pushes commits to a remote branch named main, master, production, " + "release or trunk — either named explicitly or implied because `facts.current_git_branch` is one of " + "those and the push names no other branch.",
14479
- criteria: {
14480
- true: "Commits land directly on a protected remote branch.",
14481
- false: "It pushes a feature branch whose name merely contains a word like main (e.g. feature/main-menu), " + "or it does not push."
14482
- }
14483
- }
14484
- ],
14485
- guidance: "Pushing straight to a protected branch skips review. Push a branch and open a PR unless the user asked for this."
14486
- },
14487
- {
14488
- name: "commit-on-protected-branch",
14489
- title: "Tried to commit directly on a protected branch",
14490
- appliesTo: ["shell"],
14491
- mode: "instruct",
14492
- userCanOverride: true,
14493
- precondition: (facts) => facts.currentGitBranch !== null && PROTECTED_BRANCHES.has(facts.currentGitBranch),
14494
- probes: [
14495
- {
14496
- id: "creates_commit",
14497
- instructions: "The command in `agent_request` creates or amends a git commit, merge, rebase or cherry-pick in the " + "repository (including `git -C dir commit` and `git commit -a`).",
14498
- criteria: {
14499
- true: "A new commit is written to the current branch.",
14500
- false: "It only inspects history (log, show, diff, status) or mentions `commit` in a message or search."
14501
- }
14502
- }
14503
- ],
14504
- guidance: "You are on a protected branch. Create a feature branch before committing unless the user asked otherwise."
14505
- },
14506
- {
14507
- name: "secret-exposure",
14508
- title: "Tried to read or copy credentials",
14509
- appliesTo: ["shell", "read", "write"],
14510
- mode: "deny",
14511
- userCanOverride: true,
14512
- probes: [
14513
- {
14514
- id: "touches_secrets",
14515
- instructions: "The tool call in `agent_request` reads, prints, copies, or writes credential material: SSH private " + "keys (~/.ssh/id_*), authorized_keys, cloud credentials (~/.aws, ~/.config/gcloud, ~/.azure, " + "~/.kube/config), .env files containing real secret values, API tokens, password stores, keychains, " + "or browser cookie databases.",
14516
- criteria: {
14517
- true: "Actual secret values would be read, displayed, copied or written.",
14518
- false: "It only references the concept: source code that reads process.env, a variable name, an .env.example " + "template, a test fixture, documentation about credentials, or a grep for the word."
14519
- }
14520
- }
14521
- ],
14522
- guidance: "This exposes real credentials. Ask the user before reading or copying secret material."
14523
- },
14524
- {
14525
- name: "credential-exfiltration",
14526
- title: "Tried to send secrets or private files off the machine",
14527
- appliesTo: ["shell", "network"],
14528
- mode: "deny",
14529
- userCanOverride: false,
14530
- probes: [
14531
- {
14532
- id: "sends_out",
14533
- instructions: "The tool call in `agent_request` sends data to a network destination: an HTTP POST or PUT (curl -d, " + "curl -F, curl -T, wget --post-data), scp, sftp, rsync or rclone to a remote, nc or socat, an upload to " + "a paste site, bucket or webhook, or a DNS lookup carrying encoded data.",
14534
- criteria: {
14535
- true: "Data leaves this machine.",
14536
- false: "It only downloads, or only talks to localhost."
14537
- }
14538
- },
14539
- {
14540
- id: "sensitive_payload",
14541
- instructions: "The data being sent includes credentials, keys, tokens, environment variables, or the contents of files " + "from outside the project (see `facts.paths`).",
14542
- criteria: {
14543
- true: "Secrets or private files are in what is sent.",
14544
- false: "What is sent is ordinary project data the user would expect to share, like a test request body."
14545
- }
14546
- }
14547
- ],
14548
- guidance: "This sends secrets or private files to a remote destination, which the user cannot approve. Do not send them."
14549
- },
14550
- {
14551
- name: "remote-code-execution",
14552
- title: "Tried to run code downloaded from the internet",
14553
- appliesTo: ["shell"],
14554
- mode: "deny",
14555
- userCanOverride: true,
14556
- probes: [
14557
- {
14558
- id: "download_and_run",
14559
- instructions: "The command in `agent_request` downloads code or a script from the internet and executes it: " + '`curl … | sh`, `bash <(curl …)`, `wget … -O x.sh && bash x.sh`, `python3 -c "$(curl …)"`, piping into ' + "any interpreter (sh, bash, zsh, python, node, perl, ruby), or eval of a fetched string.",
14560
- criteria: {
14561
- true: "Fetched code is executed.",
14562
- false: "It only downloads without running, runs a local file, or merely searches for or quotes such a command " + "(for example grep over a README)."
14563
- }
14564
- }
14565
- ],
14566
- exempt: {
14567
- id: "official_installer",
14568
- instructions: "The URL being executed is the documented official installer of a widely used developer tool, served from " + "that tool's own domain (for example bun.sh, sh.rustup.rs, get.docker.com, deb.nodesource.com, " + "raw.githubusercontent.com/nvm-sh/nvm, astral.sh/uv)."
14569
- },
14570
- guidance: "This executes code fetched from the internet. Download it, show it to the user, then run it."
14571
- },
14572
- {
14573
- name: "privilege-escalation",
14574
- title: "Tried to run with elevated privileges",
14575
- appliesTo: ["shell"],
14576
- mode: "deny",
14577
- userCanOverride: true,
14578
- probes: [
14579
- {
14580
- id: "elevates",
14581
- instructions: "The command in `agent_request` runs something as root or another user: sudo, doas, su, pkexec, run0, " + "`sudo -i`, `sudo -s`, including when the binary is written as an absolute path or reached through a " + "variable or wrapper.",
14582
- criteria: {
14583
- true: "Privileges are elevated.",
14584
- false: "It runs as the current user, or only mentions sudo in text, a comment, or a search pattern."
14585
- }
14586
- }
14587
- ],
14588
- guidance: "This runs with elevated privileges. Ask the user to run it themselves."
14589
- },
14590
- {
14591
- name: "database-destruction",
14592
- title: "Tried to destroy or mass-modify database data",
14593
- appliesTo: ["shell"],
14594
- mode: "deny",
14595
- userCanOverride: true,
14596
- probes: [
14597
- {
14598
- id: "destructive_sql",
14599
- instructions: "The command in `agent_request` executes SQL or a database command that drops or truncates a table, " + "schema or database, or deletes or updates rows without a condition that narrows them to specific " + "records. A condition that is always true (`WHERE 1=1`, `WHERE true`, `WHERE id > 0`) does not narrow " + "anything. Also counts: `redis-cli FLUSHALL`, `dropdb`, `mongo … dropDatabase()`.",
14600
- criteria: {
14601
- true: "Many rows, a table, or a whole database would be removed or overwritten.",
14602
- false: "It reads, inserts, or changes a specific identified record, or runs a migration file by name."
14603
- }
14604
- },
14605
- {
14606
- id: "real_database",
14607
- instructions: "The database it targets holds real data rather than being a disposable test database.",
14608
- criteria: {
14609
- true: "A real or shared database, or one whose purpose cannot be told.",
14610
- false: "Clearly an in-memory, test, fixture or throwaway local database (sqlite :memory:, a test_ database)."
14611
- }
14612
- }
14613
- ],
14614
- guidance: "This destroys database data. Confirm the target and scope with the user first."
14615
- },
14616
- {
14617
- name: "read-outside-workspace",
14618
- title: "Read files outside the project",
14619
- appliesTo: ["shell", "read"],
14620
- mode: "instruct",
14621
- userCanOverride: true,
14622
- precondition: (facts) => facts.paths.some((p) => outsideProject(facts, p)),
14623
- probes: [
14624
- {
14625
- id: "reads_outside",
14626
- instructions: "The tool call in `agent_request` reads the contents of files that `facts.paths` marks as outside the " + "project, in the user's home directory (for example another repository, Documents, or dotfiles).",
14627
- criteria: {
14628
- true: "It reads file contents from outside the project.",
14629
- false: "It only changes directory, lists names, checks existence, or touches the agent's own tool caches."
14630
- }
14631
- }
14632
- ],
14633
- guidance: "This reads files outside the current project. Mention why before continuing."
14634
- },
14635
- {
14636
- name: "agent-config-tampering",
14637
- title: "Tried to change the agent's own safety configuration",
14638
- appliesTo: ["shell", "write"],
14639
- mode: "deny",
14640
- userCanOverride: false,
14641
- probes: [
14642
- {
14643
- id: "edits_agent_config",
14644
- instructions: "The tool call in `agent_request` modifies or deletes an AI agent's own configuration, hooks, " + "permissions or guardrails: .claude/settings.json, .claude/settings.local.json, ~/.claude/, .codex/, " + ".cursor/hooks.json, .gemini/, .factory/, failproofai configuration or policies, or permission and " + "safety sections of AGENTS.md or CLAUDE.md.",
14645
- criteria: {
14646
- true: "The agent's hooks, permissions or safety settings would change.",
14647
- false: "It only reads those files, or edits ordinary project documentation."
14648
- }
14649
- }
14650
- ],
14651
- guidance: "An agent must not change its own guardrails. Ask the user to make this change."
14652
- },
14653
- {
14654
- name: "system-modification",
14655
- title: "Tried to change the system outside the project",
14656
- appliesTo: ["shell"],
14657
- mode: "instruct",
14658
- userCanOverride: true,
14659
- probes: [
14660
- {
14661
- id: "modifies_system",
14662
- instructions: "The command in `agent_request` installs or removes software system-wide or changes machine " + "configuration: apt, dnf, pacman, brew install, `npm install -g`, `pip install` outside a virtualenv, " + "editing files under /etc, systemctl enable or disable, crontab, or changing shell startup files.",
14663
- criteria: {
14664
- true: "The machine changes outside the project directory.",
14665
- false: "It installs project dependencies locally (npm install, bun install, pip install in a venv)."
14666
- }
14667
- }
14668
- ],
14669
- guidance: "This changes the machine outside the project. Mention it to the user."
14670
- },
14671
- {
14672
- name: "env-secrets-dump",
14673
- title: "Tried to print environment secrets",
14674
- appliesTo: ["shell"],
14675
- mode: "instruct",
14676
- userCanOverride: true,
14677
- probes: [
14678
- {
14679
- id: "dumps_env",
14680
- instructions: "The command in `agent_request` prints environment variable VALUES that may be secret: `env`, " + "`printenv`, `set`, `export -p`, or `echo` of a variable whose name suggests a key, token, password " + "or secret.",
14681
- criteria: {
14682
- true: "Secret-looking environment values would be printed.",
14683
- false: "It echoes a literal string, prints a non-secret variable like PATH or HOME, creates a virtualenv " + "named env, or searches code for the word export."
14684
- }
14685
- }
14686
- ],
14687
- guidance: "This prints environment values that may be secrets into the transcript."
14688
- },
14689
- {
14690
- name: "external-destructive-action",
14691
- title: "Tried an irreversible action through an external tool",
14692
- appliesTo: ["other"],
14693
- mode: "deny",
14694
- userCanOverride: true,
14695
- probes: [
14696
- {
14697
- id: "irreversible_external",
14698
- instructions: "The tool call in `agent_request` (an MCP or integration tool, see `facts.tool_name`) performs an " + "irreversible or externally visible action: deleting records, repositories, files or accounts; " + "sending email, chat or social messages on the user's behalf; making payments or purchases; merging or " + "closing pull requests; changing permissions, access or billing; or writing to a production system.",
14699
- criteria: {
14700
- true: "Something outside this machine changes in a way that cannot be quietly undone.",
14701
- false: "It reads, searches, lists, fetches, or creates a draft that nobody else sees yet."
14702
- }
14703
- }
14704
- ],
14705
- guidance: "This takes an irreversible action in an external system. Confirm with the user first."
14706
- },
14707
- {
14708
- name: "external-data-egress",
14709
- title: "Sent private data to an external tool",
14710
- appliesTo: ["other"],
14711
- mode: "instruct",
14712
- userCanOverride: true,
14713
- probes: [
14714
- {
14715
- id: "egresses_private",
14716
- instructions: "The arguments in `agent_request` send private data to an external service: source code, file " + "contents, credentials, customer data, or personal information.",
14717
- criteria: {
14718
- true: "Private data is being shared with a third party.",
14719
- false: "Only a query, identifier or public information is sent."
14720
- }
14721
- }
14722
- ],
14723
- guidance: "This shares private data with an external service. Mention what is being sent."
14724
- }
14725
- ];
14726
14576
  SCOPE_PROBE = {
14727
14577
  id: "scope",
14728
14578
  instructions: "Everything `agent_request` would affect — every branch, file, directory, table, record, environment, " + "account or recipient — is covered by what the human asked for in `user_said`. It reaches no further: not " + "all branches when they named one, not production when they said local or staging, not a parent directory " + 'when they named one folder, not every record when they named some. References such as "it", "this PR" ' + 'or "that volume" mean whatever the conversation is about. Details the agent filled in to carry the ' + "request out (a commit title, a flag the operation needs) do not make it broader.",
@@ -16916,14 +16766,13 @@ function toSemanticPolicy(entry, pack) {
16916
16766
  function semanticPoliciesFromPacks(packs) {
16917
16767
  const declared = packs.filter((p) => packSemantic(p).length > 0);
16918
16768
  if (declared.length === 0)
16919
- return { policies: SEMANTIC_POLICIES, fromPack: false, errors: [] };
16920
- const replace = replacesBuiltinChecks(declared);
16769
+ return { policies: [], fromPack: false, errors: [] };
16921
16770
  const ordered = [...declared.filter((p) => isFirstPartyPack(p)), ...declared.filter((p) => !isFirstPartyPack(p))];
16922
- const policies = replace ? [] : [...SEMANTIC_POLICIES];
16771
+ const policies = [];
16923
16772
  const errors = [];
16924
- const seen = new Set(policies.map((p) => p.name));
16773
+ const seen = new Set;
16925
16774
  const contested = contestedSemanticNames(declared);
16926
- let spent = replace ? 0 : BUILTIN_QUESTION_CHARS;
16775
+ let spent = 0;
16927
16776
  for (const pack of ordered) {
16928
16777
  for (const entry of packSemantic(pack)) {
16929
16778
  if (isReservedClaim(pack, entry.name)) {
@@ -16956,9 +16805,7 @@ function semanticPoliciesFromPacks(packs) {
16956
16805
  spent += cost;
16957
16806
  }
16958
16807
  }
16959
- if (policies.length === (replace ? 0 : SEMANTIC_POLICIES.length))
16960
- return { policies: SEMANTIC_POLICIES, fromPack: false, errors };
16961
- return { policies, fromPack: true, errors };
16808
+ return { policies, fromPack: policies.length > 0, errors };
16962
16809
  }
16963
16810
  function resolveSemanticPolicies(cli) {
16964
16811
  let packs = [];
@@ -16968,7 +16815,7 @@ function resolveSemanticPolicies(cli) {
16968
16815
  packs = read.packs;
16969
16816
  manifestErrors = read.warnings ?? [];
16970
16817
  } catch {
16971
- return SEMANTIC_POLICIES;
16818
+ return [];
16972
16819
  }
16973
16820
  const resolved = semanticPoliciesFromPacks(jevPacks(packs, cli));
16974
16821
  for (const message of [...manifestErrors, ...resolved.errors]) {
@@ -16979,7 +16826,7 @@ function resolveSemanticPolicies(cli) {
16979
16826
  }
16980
16827
  return resolved.policies;
16981
16828
  }
16982
- var GLOBAL_QUESTION_CHARS, MAX_PACK_QUESTION_CHARS, BUILTIN_QUESTION_CHARS, warned;
16829
+ var GLOBAL_QUESTION_CHARS, MAX_PACK_QUESTION_CHARS, JEV_POLICIES_QUESTION_CHARS = 18490, warned;
16983
16830
  var init_pack_policies = __esm(() => {
16984
16831
  init_effective_reviewers();
16985
16832
  init_hook_logger();
@@ -16991,7 +16838,6 @@ var init_pack_policies = __esm(() => {
16991
16838
  init_precondition_names();
16992
16839
  GLOBAL_QUESTION_CHARS = Math.max(JSON.stringify({ [INJECTION_PROBE.id]: noulOf(INJECTION_PROBE), [SCOPE_PROBE.id]: noulOf(SCOPE_PROBE) }).length, JSON.stringify(Object.fromEntries([INJECTION_PROBE, ...TASK_PROBES].map((p) => [p.id, noulOf(p)]))).length);
16993
16840
  MAX_PACK_QUESTION_CHARS = MAX_REQUEST_CHARS - MAX_STATE_CHARS - GLOBAL_QUESTION_CHARS;
16994
- BUILTIN_QUESTION_CHARS = SEMANTIC_POLICIES.reduce((n, p) => n + questionChars(p), 0);
16995
16841
  warned = new Set;
16996
16842
  });
16997
16843
 
@@ -17384,7 +17230,7 @@ var init_telemetry_id2 = __esm(() => {
17384
17230
  });
17385
17231
 
17386
17232
  // src/hooks/custom-hooks-loader.ts
17387
- import { resolve as resolve10, isAbsolute as isAbsolute2, basename as basename3 } from "node:path";
17233
+ import { resolve as resolve10, isAbsolute as isAbsolute2, basename as basename4 } from "node:path";
17388
17234
  import { randomUUID } from "crypto";
17389
17235
  import { existsSync as existsSync10, readdirSync as readdirSync3 } from "node:fs";
17390
17236
  import { readFile as readFile3 } from "node:fs/promises";
@@ -17454,7 +17300,7 @@ async function loadSingleFile(absPath, opts) {
17454
17300
  const ignored = getSemanticRegistrations().slice(semanticBefore).map((d) => d?.name);
17455
17301
  if (opts?.outsidePack && ignored.length > 0 && !warnedSemanticOutsidePack.has(absPath)) {
17456
17302
  warnedSemanticOutsidePack.add(absPath);
17457
- hookLogWarn(`${basename3(absPath)}: semanticPolicies.add only takes effect in a pack published with \`failproofai publish\`; ` + `${ignored.join(", ")} ${ignored.length === 1 ? "is" : "are"} never asked here`);
17303
+ hookLogWarn(`${basename4(absPath)}: semanticPolicies.add only takes effect in a pack published with \`failproofai publish\`; ` + `${ignored.join(", ")} ${ignored.length === 1 ? "is" : "are"} never asked here`);
17458
17304
  }
17459
17305
  if (policyModuleCache.size >= POLICY_MODULE_CACHE_MAX_ENTRIES)
17460
17306
  policyModuleCache.clear();
@@ -17470,7 +17316,7 @@ async function loadSingleFile(absPath, opts) {
17470
17316
  error_type: errorType,
17471
17317
  is_convention: !!opts?.conventionScope,
17472
17318
  convention_scope: opts?.conventionScope ?? null,
17473
- file_basename: basename3(absPath)
17319
+ file_basename: basename4(absPath)
17474
17320
  });
17475
17321
  if (opts?.strict)
17476
17322
  throw new Error(`Failed to load custom hooks from ${absPath}: ${msg}`);
@@ -17659,12 +17505,12 @@ async function loadAllCustomHooks(customPoliciesPaths, opts) {
17659
17505
  await loadSingleFile(file, { conventionScope: projectScope, outsidePack: true });
17660
17506
  const newHooks = getCustomHooks().slice(hooksBefore);
17661
17507
  for (const hook of newHooks) {
17662
- hook.__policyId = conventionPolicyId(projectScope, basename3(file), hook.name);
17508
+ hook.__policyId = conventionPolicyId(projectScope, basename4(file), hook.name);
17663
17509
  }
17664
17510
  if (newHooks.length > 0) {
17665
17511
  conventionSources.push({
17666
17512
  scope: projectScope,
17667
- file: basename3(file),
17513
+ file: basename4(file),
17668
17514
  hookNames: newHooks.map((h) => h.name)
17669
17515
  });
17670
17516
  }
@@ -17681,12 +17527,12 @@ async function loadAllCustomHooks(customPoliciesPaths, opts) {
17681
17527
  await loadSingleFile(file, { conventionScope: "user", outsidePack: true });
17682
17528
  const newHooks = getCustomHooks().slice(hooksBefore);
17683
17529
  for (const hook of newHooks) {
17684
- hook.__policyId = conventionPolicyId("user", basename3(file), hook.name);
17530
+ hook.__policyId = conventionPolicyId("user", basename4(file), hook.name);
17685
17531
  }
17686
17532
  if (newHooks.length > 0) {
17687
17533
  conventionSources.push({
17688
17534
  scope: "user",
17689
- file: basename3(file),
17535
+ file: basename4(file),
17690
17536
  hookNames: newHooks.map((h) => h.name)
17691
17537
  });
17692
17538
  }
@@ -19318,14 +19164,14 @@ function combineTwoTier(verdicts, review, mode) {
19318
19164
  ...review.model ? { jevModel: review.model } : {},
19319
19165
  jevMode: mode
19320
19166
  };
19321
- if (mode === "shadow") {
19322
- const shadowVerdict = review.decision === "deny" || review.decision === "instruct" ? {
19167
+ if (mode === "observe") {
19168
+ const observeVerdict = review.decision === "deny" || review.decision === "instruct" ? {
19323
19169
  policyName: jevEntry.policyName,
19324
19170
  decision: review.decision,
19325
19171
  reason: jevEntry.reason,
19326
19172
  version: review.model && JEV_MODEL_RE.test(review.model) ? review.model : "jev"
19327
19173
  } : undefined;
19328
- return { final: legacy, cleared, decidedByJev: false, activity, ...shadowVerdict ? { shadowVerdict } : {} };
19174
+ return { final: legacy, cleared, decidedByJev: false, activity, ...observeVerdict ? { observeVerdict } : {} };
19329
19175
  }
19330
19176
  return { ...enforced, cleared, activity };
19331
19177
  }
@@ -19419,7 +19265,7 @@ async function evaluatePolicies(eventType, payload, session, config, twoTier) {
19419
19265
  }
19420
19266
  const combined = combineTwoTier(collected.verdicts, review, twoTier.mode);
19421
19267
  if (combined.cleared.length > 0) {
19422
- hookLogInfo(`jev ${twoTier.mode === "shadow" ? "would clear" : "cleared"}: ${combined.cleared.join(", ")}`);
19268
+ hookLogInfo(`jev ${twoTier.mode === "observe" ? "would clear" : "cleared"}: ${combined.cleared.join(", ")}`);
19423
19269
  }
19424
19270
  if (combined.activity.evaluator === "jev-fallback") {
19425
19271
  hookLogInfo(combined.activity.jevFallbackReason === JEV_REASON_REQUEST_CUT ? "jev could not be shown the whole call; it cleared nothing, and the call was not allowed on a partial read" : `jev unavailable (${combined.activity.jevFallbackReason}); the regex result decided this call`);
@@ -19430,7 +19276,7 @@ async function evaluatePolicies(eventType, payload, session, config, twoTier) {
19430
19276
  activity: combined.activity,
19431
19277
  decidedByJev: combined.decidedByJev,
19432
19278
  ...combined.decidedByJev && review.kind === "answered" && review.origin ? { jevOrigin: review.origin } : {},
19433
- ...combined.shadowVerdict ? { shadowVerdict: combined.shadowVerdict } : {}
19279
+ ...combined.observeVerdict ? { observeVerdict: combined.observeVerdict } : {}
19434
19280
  }
19435
19281
  };
19436
19282
  }
@@ -24798,7 +24644,7 @@ var init_copilot_sessions = __esm(() => {
24798
24644
  // lib/cursor-sessions.ts
24799
24645
  import { readFileSync as readFileSync13, readdirSync as readdirSync11, existsSync as existsSync18, statSync as statSync13 } from "node:fs";
24800
24646
  import { readFile as readFile13 } from "node:fs/promises";
24801
- import { basename as basename4, join as join28, resolve as resolve14, sep as sep3 } from "node:path";
24647
+ import { basename as basename5, join as join28, resolve as resolve14, sep as sep3 } from "node:path";
24802
24648
  import { homedir as homedir20 } from "node:os";
24803
24649
  function getCursorHome2() {
24804
24650
  return process.env.CURSOR_HOME || join28(homedir20(), ".cursor");
@@ -24836,7 +24682,7 @@ function findCursorTranscript(sessionId) {
24836
24682
  const dir = getCursorSessionDir(sessionId);
24837
24683
  if (!dir)
24838
24684
  return null;
24839
- const newCandidate = join28(dir, `${basename4(dir)}.jsonl`);
24685
+ const newCandidate = join28(dir, `${basename5(dir)}.jsonl`);
24840
24686
  if (existsSync18(newCandidate))
24841
24687
  return newCandidate;
24842
24688
  for (const name of LEGACY_TRANSCRIPT_FILE_CANDIDATES2) {
@@ -27065,6 +26911,9 @@ var init_fp_reset = __esm(() => {
27065
26911
  // src/hooks/semantic/jev-config.ts
27066
26912
  import { closeSync as closeSync2, constants as fsConstants2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2, statSync as statSync18 } from "node:fs";
27067
26913
  import { dirname as dirname12 } from "node:path";
26914
+ function parseJevMode(raw) {
26915
+ return raw === "off" || raw === "observe" || raw === "enforce" ? raw : null;
26916
+ }
27068
26917
  function jevCloudBaseUrl(cloudBase) {
27069
26918
  const url = new URL(cloudBase);
27070
26919
  return `${url.origin}${url.pathname.replace(/\/+$/, "")}${JEV_CLOUD_BASE_PATH}`;
@@ -27288,14 +27137,15 @@ function validateJevConfig(raw, envKey, cloud) {
27288
27137
  cfg.timeoutMs = t;
27289
27138
  }
27290
27139
  if (o.mode !== undefined) {
27291
- if (o.mode !== "off" && o.mode !== "shadow" && o.mode !== "enforce")
27292
- return { ok: false, problem: 'mode must be "off", "shadow" or "enforce"' };
27293
- cfg.mode = o.mode;
27140
+ const mode = parseJevMode(o.mode);
27141
+ if (mode === null)
27142
+ return { ok: false, problem: 'mode must be "off", "observe" or "enforce"' };
27143
+ cfg.mode = mode;
27294
27144
  }
27295
27145
  if (cfg.baseUrl !== undefined && isPlainHttp(cfg.baseUrl) && cfg.mode === "enforce") {
27296
27146
  return {
27297
27147
  ok: false,
27298
- problem: "plain http (to localhost) is accepted only with mode shadow: in enforce mode Jev's answers can clear a deny, " + "and while the local proxy is down any process on this machine could take its port and answer. " + (cfg.provider === JEV_CLOUD_PROVIDER ? "Reconnect to an https FailproofAI Cloud URL (failproofai config --token <key> --url https://…), or keep mode shadow" : "Use https, or mode shadow")
27148
+ problem: "plain http (to localhost) is accepted only with mode observe: in enforce mode Jev's answers can clear a deny, " + "and while the local proxy is down any process on this machine could take its port and answer. " + (cfg.provider === JEV_CLOUD_PROVIDER ? "Reconnect to an https FailproofAI Cloud URL (failproofai config --token <key> --url https://…), or keep mode observe" : "Use https, or mode observe")
27299
27149
  };
27300
27150
  }
27301
27151
  return { ok: true, value: cfg };
@@ -29733,7 +29583,7 @@ function authorityOf(p) {
29733
29583
  return { authority, reviewedBy };
29734
29584
  }
29735
29585
  function resolveMode(cfg) {
29736
- return cfg.mode === "shadow" || cfg.mode === "enforce" ? cfg.mode : DEFAULT_JEV_MODE;
29586
+ return cfg.mode === "observe" || cfg.mode === "enforce" ? cfg.mode : DEFAULT_JEV_MODE;
29737
29587
  }
29738
29588
  function resolveTimeout(cfg) {
29739
29589
  const t = cfg.timeoutMs;
@@ -29851,7 +29701,7 @@ function startJevReview(cfg, call) {
29851
29701
  sessionId: call.sessionId,
29852
29702
  cli: call.cli,
29853
29703
  eventType: call.eventType,
29854
- applied: abandoned || review.kind !== "answered" ? "legacy-fallback" : mode === "shadow" ? "shadow" : "two-tier"
29704
+ applied: abandoned || review.kind !== "answered" ? "legacy-fallback" : mode === "observe" ? "observe" : "two-tier"
29855
29705
  });
29856
29706
  appendVerdictLog(hit ? { ...row, cached: true } : row);
29857
29707
  }
@@ -30102,6 +29952,8 @@ async function startTwoTier(canonicalEventType, parsed, session, cli, opts, acti
30102
29952
  return null;
30103
29953
  if (jevForcedOff(opts) || activePause)
30104
29954
  return null;
29955
+ if (!jevChecksInstalled())
29956
+ return null;
30105
29957
  const loaded = await readJevConfig();
30106
29958
  if (!loaded)
30107
29959
  return null;
@@ -30120,7 +29972,7 @@ async function startTwoTier(canonicalEventType, parsed, session, cli, opts, acti
30120
29972
  } catch (err) {
30121
29973
  hookLogInfo(`Jev review could not start (${err instanceof Error ? err.message : String(err)})`);
30122
29974
  return {
30123
- mode: cfg.mode === "shadow" || cfg.mode === "enforce" ? cfg.mode : loaded.defaultMode,
29975
+ mode: cfg.mode === "observe" || cfg.mode === "enforce" ? cfg.mode : loaded.defaultMode,
30124
29976
  review: Promise.resolve({ kind: "fallback", reason: "error", latencyMs: null, model: null }),
30125
29977
  abort: () => {},
30126
29978
  authorityOf: () => ({ authority: "hard", reviewedBy: [] })
@@ -30144,6 +29996,8 @@ async function captureJevIntent(canonicalEventType, decision, parsed, session, c
30144
29996
  if (canonicalEventType !== "UserPromptSubmit" || jevForcedOff(opts))
30145
29997
  return;
30146
29998
  try {
29999
+ if (!jevChecksInstalled())
30000
+ return;
30147
30001
  if (!await readJevConfig())
30148
30002
  return;
30149
30003
  if (decision === "deny") {
@@ -30224,7 +30078,7 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
30224
30078
  config = readMergedHooksConfig(session.cwd);
30225
30079
  clearPolicies(cli);
30226
30080
  activePause = readActivePause(session.sessionId);
30227
- const packsInstalledHere = hasInstalledPacks();
30081
+ const packsInstalledHere = hasInstalledRegexPacks();
30228
30082
  const legacyNames = activePause || packsInstalledHere ? [] : config.enabledPolicies;
30229
30083
  registerBuiltinPolicies(activePause ? [] : legacyNames);
30230
30084
  if (legacyNames.length > 0) {
@@ -30298,14 +30152,14 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
30298
30152
  const observeOnly = cloudManaged?.effect === "observe" || pack?.effect === "observe";
30299
30153
  const fn = async (ctx) => {
30300
30154
  if (observeOnly) {
30301
- const shadow = await runObserved(hook, ctx, hookName, eventType, cli);
30302
- if (shadow.decision !== "allow") {
30155
+ const observed = await runObserved(hook, ctx, hookName, eventType, cli);
30156
+ if (observed.decision !== "allow") {
30303
30157
  const observer = cloudManaged ?? pack;
30304
30158
  observedResults.push({
30305
30159
  policyId: observer.id,
30306
30160
  version: observer.version,
30307
- decision: shadow.decision,
30308
- reason: shadow.reason ?? null
30161
+ decision: observed.decision,
30162
+ reason: observed.reason ?? null
30309
30163
  });
30310
30164
  }
30311
30165
  return { decision: "allow" };
@@ -30395,13 +30249,13 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
30395
30249
  ...twoTier,
30396
30250
  releaseRegistry: opts?.releaseRegistry
30397
30251
  }) : await evaluatePolicies(canonicalEventType, parsed, session, config);
30398
- const shadowVerdict = result.twoTier?.shadowVerdict;
30399
- if (shadowVerdict) {
30252
+ const observeVerdict = result.twoTier?.observeVerdict;
30253
+ if (observeVerdict) {
30400
30254
  observedResults.push({
30401
- policyId: shadowVerdict.policyName,
30402
- version: shadowVerdict.version,
30403
- decision: shadowVerdict.decision,
30404
- reason: shadowVerdict.reason
30255
+ policyId: observeVerdict.policyName,
30256
+ version: observeVerdict.version,
30257
+ decision: observeVerdict.decision,
30258
+ reason: observeVerdict.reason
30405
30259
  });
30406
30260
  }
30407
30261
  const durationMs = Math.round(performance.now() - startTime);
@@ -31324,7 +31178,7 @@ import {
31324
31178
  rmSync as rmSync9,
31325
31179
  writeFileSync as writeFileSync14
31326
31180
  } from "node:fs";
31327
- import { basename as basename5, dirname as dirname16, join as join32, resolve as resolve24 } from "node:path";
31181
+ import { basename as basename6, dirname as dirname16, join as join32, resolve as resolve24 } from "node:path";
31328
31182
  function migrateToLayout4() {
31329
31183
  const moves = [
31330
31184
  { from: legacy.authJson(), to: auditSessionFile() },
@@ -31360,7 +31214,7 @@ function migrateToLayout4() {
31360
31214
  }
31361
31215
  rmSync9(from, { force: true });
31362
31216
  }
31363
- migrated.push(`${basename5(from)} → audit/${basename5(to)}`);
31217
+ migrated.push(`${basename6(from)} → audit/${basename6(to)}`);
31364
31218
  }
31365
31219
  for (const secret of [auditSessionFile()]) {
31366
31220
  if (!existsSync29(secret))
@@ -31404,7 +31258,7 @@ function appendLedger(entry) {
31404
31258
  } catch {}
31405
31259
  }
31406
31260
  function backupNameOf(f) {
31407
- return f.as ?? basename5(f.at());
31261
+ return f.as ?? basename6(f.at());
31408
31262
  }
31409
31263
  function pruneMigratedCredentials(from) {
31410
31264
  const pruned = [];
@@ -31553,7 +31407,7 @@ var init_migrations = __esm(() => {
31553
31407
  { at: legacy.auditSchedule }
31554
31408
  ];
31555
31409
  PRUNED_AFTER_SUCCESS = [
31556
- { as: basename5(legacy.authJson()), landsAt: auditSessionFile }
31410
+ { as: basename6(legacy.authJson()), landsAt: auditSessionFile }
31557
31411
  ];
31558
31412
  });
31559
31413
 
@@ -32005,7 +31859,7 @@ var init_cli_error = __esm(() => {
32005
31859
  // src/hooks/manager.ts
32006
31860
  import { execSync as execSync6 } from "node:child_process";
32007
31861
  import { existsSync as existsSync31 } from "node:fs";
32008
- import { resolve as resolve26, basename as basename6 } from "node:path";
31862
+ import { resolve as resolve26, basename as basename7 } from "node:path";
32009
31863
  import { platform as platform3, arch as arch3, release as release2, hostname as hostname3 } from "node:os";
32010
31864
  function getSettingsPath(scope, cwd) {
32011
31865
  return claudeCode.getSettingsPath(scope, cwd);
@@ -32280,7 +32134,7 @@ Validated ${validatedHooks.length} custom hook(s) from ${path}: ${validatedHooks
32280
32134
  const alwaysOnNames = new Set(BUILTIN_POLICIES.filter((p) => p.alwaysOn).map((p) => p.name));
32281
32135
  const fromPack = policyNames === undefined ? [] : selectedPolicies.filter((name) => !alwaysOnNames.has(name));
32282
32136
  if (fromPack.length > 0) {
32283
- if (!hasInstalledPacks()) {
32137
+ if (!hasInstalledRegexPacks()) {
32284
32138
  try {
32285
32139
  await addPack(CORE_SOURCE, { only: fromPack });
32286
32140
  } catch (err) {
@@ -32999,7 +32853,7 @@ function fieldsOf(file) {
32999
32853
  } catch {}
33000
32854
  }
33001
32855
  }
33002
- var CLOUD_JEV_INITIAL_MODE = "shadow", MAX_JEV_CONFIG_BYTES;
32856
+ var CLOUD_JEV_INITIAL_MODE = "observe", MAX_JEV_CONFIG_BYTES;
33003
32857
  var init_jev_cloud_connection = __esm(() => {
33004
32858
  init_jev_config();
33005
32859
  MAX_JEV_CONFIG_BYTES = 64 * 1024;
@@ -33134,18 +32988,19 @@ function jevLines(outcome) {
33134
32988
  if (!config || config.status === "written") {
33135
32989
  const plainHttp = config?.status === "written" && new URL(config.baseUrl).protocol === "http:";
33136
32990
  return [
33137
- ` Jev on through FailproofAI Cloud, in shadow mode: logged, not enforced (${config?.path ?? "jev.json"}).`,
33138
- plainHttp ? " Enforce needs an https FailproofAI Cloud URL: reconnect with `failproofai config --token <key> --url https://…`. Over plain http Jev stays in shadow mode." : " Enforce it with `failproofai jev setup --mode enforce`, or from the dashboard."
32991
+ ` Jev on through FailproofAI Cloud, in observe mode: logged, not enforced (${config?.path ?? "jev.json"}).`,
32992
+ ...jevChecksDeclared() ? [] : [" It has no checks installed yet, so it asks nothing. To enable it: `failproofai policies add FailproofAI/jev-policies`."],
32993
+ plainHttp ? " Enforce needs an https FailproofAI Cloud URL: reconnect with `failproofai config --token <key> --url https://…`. Over plain http Jev stays in observe mode." : " Enforce it with `failproofai jev setup --mode enforce`, or from the dashboard."
33139
32994
  ];
33140
32995
  }
33141
32996
  if (config.status === "kept") {
33142
32997
  const lines = [` Jev key stored; ${config.path} already exists and was left as configured.`];
33143
32998
  if (config.otherOrigin) {
33144
- lines.push(config.jevOff?.why === "off" ? ` It points at ${config.otherOrigin} and has Jev switched off (mode off), so Jev is off. To point it here and turn it on: \`failproofai jev setup --provider failproofai --mode shadow\`.` : ` It points at ${config.otherOrigin}, so Jev stays off until you run \`failproofai jev setup --provider failproofai\`.`);
32999
+ lines.push(config.jevOff?.why === "off" ? ` It points at ${config.otherOrigin} and has Jev switched off (mode off), so Jev is off. To point it here and turn it on: \`failproofai jev setup --provider failproofai --mode observe\`.` : ` It points at ${config.otherOrigin}, so Jev stays off until you run \`failproofai jev setup --provider failproofai\`.`);
33145
33000
  } else if (config.jevOff?.why === "refused") {
33146
33001
  lines.push(` It was refused (${config.jevOff.problem}), so Jev is off. ${config.jevOff.fix ? `Fix: \`${config.jevOff.fix}\`.` : "Write a valid one: `failproofai jev setup --provider failproofai`."}`);
33147
33002
  } else if (config.jevOff?.why === "off") {
33148
- lines.push(" It has Jev switched off (mode off), so Jev is off. To turn it on: `failproofai jev setup --mode shadow`.");
33003
+ lines.push(" It has Jev switched off (mode off), so Jev is off. To turn it on: `failproofai jev setup --mode observe`.");
33149
33004
  }
33150
33005
  if (jev.stillOn) {
33151
33006
  lines.push(` Jev is still on through FailproofAI Cloud (${jev.stillOn} mode): it sends each checked tool call and the recent prompt to FailproofAI Cloud. To switch it off: \`failproofai jev setup --mode off\`.`);
@@ -33207,11 +33062,12 @@ var init_cloud_connection = __esm(() => {
33207
33062
  init_cloud_enrollment();
33208
33063
  init_fp_home();
33209
33064
  init_fp_config();
33065
+ init_effective_reviewers();
33210
33066
  init_collector_config();
33211
33067
  });
33212
33068
 
33213
33069
  // src/hooks/setup-state.ts
33214
- import { existsSync as existsSync34, realpathSync as realpathSync2, statSync as statSync24 } from "node:fs";
33070
+ import { existsSync as existsSync34, realpathSync as realpathSync3, statSync as statSync24 } from "node:fs";
33215
33071
  import { dirname as dirname19, resolve as resolve27 } from "node:path";
33216
33072
  import { homedir as homedir26 } from "node:os";
33217
33073
  function globalConfigPath(home = homedir26()) {
@@ -33255,7 +33111,7 @@ function hasGlobalHooksInstalled() {
33255
33111
  function realResolve(path) {
33256
33112
  const lexical = resolve27(path);
33257
33113
  try {
33258
- return realpathSync2(lexical);
33114
+ return realpathSync3(lexical);
33259
33115
  } catch {
33260
33116
  return lexical;
33261
33117
  }
@@ -33426,7 +33282,7 @@ var init_onboarding_lock = __esm(() => {
33426
33282
 
33427
33283
  // lib/claude-sessions.ts
33428
33284
  import { readdirSync as readdirSync19, statSync as statSync25, existsSync as existsSync36 } from "node:fs";
33429
- import { join as join34, basename as basename7 } from "node:path";
33285
+ import { join as join34, basename as basename8 } from "node:path";
33430
33286
  function getClaudeProjectsRoot() {
33431
33287
  return getClaudeProjectsPath();
33432
33288
  }
@@ -37564,7 +37420,7 @@ function surveyReviewableCoverage(cwd) {
37564
37420
  records.push(...selected ? pack.policies.filter((p) => selected.includes(p.name)) : pack.policies);
37565
37421
  }
37566
37422
  const asked = semanticPoliciesFromPacks(jevPacks(packs));
37567
- reviewers = asked.fromPack ? new Set(asked.policies.map((p) => p.name)) : SEMANTIC_REVIEWER_NAMES;
37423
+ reviewers = new Set(asked.policies.map((p) => p.name));
37568
37424
  } catch {}
37569
37425
  let config;
37570
37426
  try {
@@ -37596,7 +37452,7 @@ function surveyReviewableCoverage(cwd) {
37596
37452
  } catch {
37597
37453
  customFiles = 0;
37598
37454
  }
37599
- return { ...countReviewable(records, reviewers), customFiles };
37455
+ return { ...countReviewable(records, reviewers), customFiles, jevChecks: reviewers?.size ?? 0 };
37600
37456
  }
37601
37457
  function reviewableSummary(coverage) {
37602
37458
  const { enabled, reviewable } = coverage;
@@ -37609,11 +37465,13 @@ function reviewableSummary(coverage) {
37609
37465
  return `${reviewable} of ${noun} reviewable: Jev may clear a deny or an instruction from those, and from no others${tail}.`;
37610
37466
  }
37611
37467
  function reviewableProblem(coverage) {
37468
+ if (coverage.jevChecks === 0)
37469
+ return NO_JEV_CHECKS_PROBLEM;
37612
37470
  if (coverage.enabled === 0 || coverage.reviewable > 0 || coverage.customFiles > 0)
37613
37471
  return null;
37614
37472
  return "Jev can add a deny or an instruction on this machine, but it can never clear one. " + "No enabled policy is marked reviewable — a policy pack published before this release carries no such marks — " + `so re-take the pack (\`${RETAKE_PACK_COMMAND}\`) to get a marked copy, ` + "or enforce this build's builtin policies, which carry them.";
37615
37473
  }
37616
- var RETAKE_PACK_COMMAND = "failproofai policies add FailproofAI/policies";
37474
+ var RETAKE_PACK_COMMAND = "failproofai policies add FailproofAI/policies", JEV_POLICIES_ADD_COMMAND = "failproofai policies add FailproofAI/jev-policies", NO_JEV_CHECKS_PROBLEM;
37617
37475
  var init_policy_reviewability = __esm(() => {
37618
37476
  init_cloud_managed_policies();
37619
37477
  init_effective_reviewers();
@@ -37625,6 +37483,7 @@ var init_policy_reviewability = __esm(() => {
37625
37483
  init_policy_catalog();
37626
37484
  init_policy_registry();
37627
37485
  init_pack_policies();
37486
+ NO_JEV_CHECKS_PROBLEM = `Jev has no checks installed, so it asks nothing and clears nothing. To enable it: \`${JEV_POLICIES_ADD_COMMAND}\`.`;
37628
37487
  });
37629
37488
 
37630
37489
  // src/hooks/semantic/jev-stats.ts
@@ -37670,14 +37529,14 @@ function computeJevStats(entries, opts = {}) {
37670
37529
  notConsulted: 0,
37671
37530
  noRequest: 0,
37672
37531
  decisions: { allow: 0, instruct: 0, deny: 0 },
37673
- modes: { shadow: 0, enforce: 0 },
37674
- shadowClearsByPolicy: {},
37532
+ modes: { observe: 0, enforce: 0 },
37533
+ observeClearsByPolicy: {},
37675
37534
  models: {}
37676
37535
  };
37677
37536
  const latencies = [];
37678
37537
  const fallbackReasons = new Counter;
37679
37538
  const clearsByPolicy = new Counter;
37680
- const shadowClearsByPolicy = new Counter;
37539
+ const observeClearsByPolicy = new Counter;
37681
37540
  const models = new Counter;
37682
37541
  for (const raw of entries) {
37683
37542
  if (typeof raw.timestamp !== "number" || raw.timestamp < since || raw.timestamp > now)
@@ -37709,13 +37568,13 @@ function computeJevStats(entries, opts = {}) {
37709
37568
  latencies.push(e.jevLatencyMs);
37710
37569
  if (e.jevModel)
37711
37570
  models.bump(e.jevModel);
37712
- const clears = e.jevMode === "shadow" ? shadowClearsByPolicy : clearsByPolicy;
37571
+ const clears = e.jevMode === "observe" ? observeClearsByPolicy : clearsByPolicy;
37713
37572
  for (const name of e.jevCleared ?? [])
37714
37573
  clears.bump(name);
37715
37574
  }
37716
37575
  stats.fallbackReasons = fallbackReasons.toRecord();
37717
37576
  stats.clearsByPolicy = clearsByPolicy.toRecord();
37718
- stats.shadowClearsByPolicy = shadowClearsByPolicy.toRecord();
37577
+ stats.observeClearsByPolicy = observeClearsByPolicy.toRecord();
37719
37578
  stats.models = models.toRecord();
37720
37579
  stats.fallbackRate = stats.total > 0 ? stats.fallbacks / stats.total : 0;
37721
37580
  latencies.sort((a, b) => a - b);
@@ -37781,7 +37640,7 @@ function parseFlags(argv, allowed) {
37781
37640
  function modeLine(mode) {
37782
37641
  if (mode === "off")
37783
37642
  return "off — Jev is not asked at all; the regex policies decide alone";
37784
- return mode === "enforce" ? "enforce — Jev's verdicts apply: it may clear a reviewable policy's deny and add its own" : "shadow — Jev is asked and logged; the regex result is what is enforced";
37643
+ return mode === "enforce" ? "enforce — Jev's verdicts apply: it may clear a reviewable policy's deny and add its own" : "observe — Jev is asked and logged; the regex result is what is enforced";
37785
37644
  }
37786
37645
  function providerLabel(provider) {
37787
37646
  return provider === JEV_CLOUD_PROVIDER ? "FailproofAI Cloud" : provider;
@@ -37912,14 +37771,14 @@ function jevStatsLines(stats, opts = {}) {
37912
37771
  const reasons = Object.entries(stats.fallbackReasons).sort((a, b) => b[1] - a[1]).map(([r, n]) => `${r} ×${n}`).join(", ");
37913
37772
  const byPolicy = (counts) => Object.entries(counts ?? {}).sort((a, b) => b[1] - a[1]).map(([p, n]) => `${p} ×${n}`).join(", ");
37914
37773
  const clears = byPolicy(stats.clearsByPolicy);
37915
- const shadowClears = byPolicy(stats.shadowClearsByPolicy);
37774
+ const observeClears = byPolicy(stats.observeClearsByPolicy);
37916
37775
  const ms = (v) => v === null ? "—" : `${Math.round(v)} ms`;
37917
37776
  return stack(heading, rows([
37918
37777
  ["evaluations", String(stats.total)],
37919
37778
  ["fell back to regex", `${pct(stats.fallbackRate)}${reasons ? ` (${reasons})` : ""}`],
37920
37779
  ["latency", `p50 ${ms(stats.latencyP50Ms)} · p95 ${ms(stats.latencyP95Ms)}`],
37921
37780
  ["cleared", clears || "nothing"],
37922
- ...shadowClears ? [["would have cleared (shadow)", shadowClears]] : []
37781
+ ...observeClears ? [["would have cleared (observe)", observeClears]] : []
37923
37782
  ], opts));
37924
37783
  }
37925
37784
  function originOf2(provider, baseUrl) {
@@ -38371,8 +38230,7 @@ async function cloudSetup(values, bools, opts) {
38371
38230
  delete next.accountId;
38372
38231
  if (!sameProvider && existing?.raw.timeoutMs !== undefined)
38373
38232
  next.timeoutMs = existing.raw.timeoutMs;
38374
- const mode = values.get("--mode") ?? (sameProvider && typeof next.mode === "string" ? next.mode : "shadow");
38375
- next.mode = mode;
38233
+ next.mode = values.get("--mode") ?? (sameProvider && typeof next.mode === "string" ? next.mode : "observe");
38376
38234
  if (values.has("--timeout-ms")) {
38377
38235
  const rawTimeout = values.get("--timeout-ms");
38378
38236
  const n = Number(rawTimeout);
@@ -38401,7 +38259,7 @@ async function cloudSetup(values, bools, opts) {
38401
38259
  `Reconnect with this machine's key to re-check what it carries (an older failproofai connected without storing its Jev key; a key without jev:evaluate needs the "machine" preset on the dashboard's Keys page):`
38402
38260
  ] : [
38403
38261
  "Not saved: this machine is not connected to FailproofAI Cloud with a key that carries jev:evaluate.",
38404
- 'Connect it with one that does (the "machine" preset) — that also turns Jev on, in shadow mode, when there is no jev.json yet:'
38262
+ 'Connect it with one that does (the "machine" preset) — that also turns Jev on, in observe mode, when there is no jev.json yet:'
38405
38263
  ],
38406
38264
  " failproofai config --token <key>",
38407
38265
  "",
@@ -38468,7 +38326,7 @@ function displayOnlyCloudOrigin(routing) {
38468
38326
  function namedEndpoint(raw) {
38469
38327
  if (!raw || typeof raw.provider !== "string" || !JEV_PROVIDER_KINDS.includes(raw.provider))
38470
38328
  return null;
38471
- const routing = { provider: raw.provider, apiKey: ENV_KEY_STAND_IN, mode: "shadow", ...displayOnlyCloudOrigin(raw) };
38329
+ const routing = { provider: raw.provider, apiKey: ENV_KEY_STAND_IN, mode: "observe", ...displayOnlyCloudOrigin(raw) };
38472
38330
  if (raw.baseUrl !== undefined)
38473
38331
  routing.baseUrl = raw.baseUrl;
38474
38332
  if (raw.accountId !== undefined)
@@ -38577,6 +38435,7 @@ async function status(argv, opts) {
38577
38435
  enabled: coverage.enabled,
38578
38436
  reviewable: coverage.reviewable,
38579
38437
  customPolicyFiles: coverage.customFiles,
38438
+ jevChecks: coverage.jevChecks,
38580
38439
  problem: coverageProblem
38581
38440
  }
38582
38441
  } : {}
@@ -38619,7 +38478,7 @@ async function status(argv, opts) {
38619
38478
  ["mode", modeLine("off")],
38620
38479
  ["config", inspection.path],
38621
38480
  ...r.provider === JEV_CLOUD_PROVIDER ? [["key", cloudKeyRow()]] : []
38622
- ], opts), nextStep("failproofai jev setup --mode shadow", "Switch it back on (shadow logs Jev and keeps enforcing regex; enforce lets it clear), here or from the dashboard:", opts), legacyNote, jevStatsLines(stats, opts)));
38481
+ ], opts), nextStep("failproofai jev setup --mode observe", "Switch it back on (observe logs Jev and keeps enforcing regex; enforce lets it clear), here or from the dashboard:", opts), legacyNote, jevStatsLines(stats, opts)));
38623
38482
  }
38624
38483
  if (inspection.status === "not-connected") {
38625
38484
  const r = inspection.routing;
@@ -38691,7 +38550,7 @@ async function test(argv, deps, opts) {
38691
38550
  const why = inspection.status === "absent" ? `There is no ${inspection.path}; nothing to test.` : inspection.status === "key-missing" ? `${inspection.path} takes its key from ${JEV_API_KEY_ENV}, which is not set in this shell, so there is no key to test with.` : inspection.status === "off" ? `Jev is switched off in ${inspection.path} (mode off), so nothing is sent.` : inspection.status === "not-connected" ? `${inspection.path} sends Jev requests through FailproofAI Cloud, and this machine is not connected to it with a key that carries jev:evaluate.` : inspection.status === "key-lacks-jev" ? `${inspection.path} sends Jev requests through FailproofAI Cloud, and no Jev key is stored for this machine's connection.` : `${inspection.path} was refused — ${inspection.problem}.`;
38692
38551
  const code = inspection.status === "absent" ? "not-configured" : inspection.status === "key-missing" ? "no-env-key" : inspection.status === "off" ? "switched-off" : inspection.status === "not-connected" || inspection.status === "key-lacks-jev" ? inspection.status : "config";
38693
38552
  const refusedNext = inspection.status === "refused" ? refusedNextStep(inspection, cloudRoute(readJevConfigFileForUpdate()?.raw)) : inspection.status === "absent" && cloudCredentialPresent() ? { cmd: "failproofai jev setup --provider failproofai", lead: "This machine's FailproofAI Cloud key already carries jev:evaluate. Turn it on:" } : null;
38694
- const fixCmd = refusedNext ? refusedNext.cmd : inspection.status === "key-missing" ? "failproofai jev setup --key-stdin < key-file" : inspection.status === "off" ? "failproofai jev setup --mode shadow" : inspection.status === "not-connected" || inspection.status === "key-lacks-jev" ? "failproofai config --token <key>" : "failproofai jev setup --provider <kind> --key-stdin";
38553
+ const fixCmd = refusedNext ? refusedNext.cmd : inspection.status === "key-missing" ? "failproofai jev setup --key-stdin < key-file" : inspection.status === "off" ? "failproofai jev setup --mode observe" : inspection.status === "not-connected" || inspection.status === "key-lacks-jev" ? "failproofai config --token <key>" : "failproofai jev setup --provider <kind> --key-stdin";
38695
38554
  const fixLead = refusedNext ? refusedNext.lead : inspection.status === "key-missing" ? `Set ${JEV_API_KEY_ENV} for this shell, or store the key in the file:` : inspection.status === "off" ? "Switch it back on:" : inspection.status === "not-connected" ? "Connect with a key that carries jev:evaluate:" : inspection.status === "key-lacks-jev" ? `Reconnect with this machine's key to re-check what it carries (a key without jev:evaluate needs the "machine" preset on the dashboard's Keys page):` : undefined;
38696
38555
  const json = asJson ? JSON.stringify({ ok: false, error: { code, message: why } }, null, 2) : undefined;
38697
38556
  return fail2(stack(title("failproofai jev test", "not run", opts), note(why, opts), nextStep(fixCmd, fixLead, opts)), json);
@@ -38920,7 +38779,7 @@ var init_jev_cli = __esm(() => {
38920
38779
  "Usage:",
38921
38780
  " failproofai jev --url <url> [--key-stdin | --token <token>] [options]",
38922
38781
  " failproofai jev setup --provider <kind> [--key-stdin | --key-from-env] [options]",
38923
- " failproofai jev setup --provider failproofai [--mode off|shadow|enforce] (FailproofAI Cloud: no key, no URL)",
38782
+ " failproofai jev setup --provider failproofai [--mode off|observe|enforce] (FailproofAI Cloud: no key, no URL)",
38924
38783
  " failproofai jev status [--json]",
38925
38784
  " failproofai jev test [--json]",
38926
38785
  " failproofai jev models [--provider <kind>] [--url <base>] [--json]",
@@ -39030,7 +38889,7 @@ import {
39030
38889
  rmSync as rmSync16,
39031
38890
  writeFileSync as writeFileSync26
39032
38891
  } from "node:fs";
39033
- import { basename as basename8, dirname as dirname25, join as join39, resolve as resolve32 } from "node:path";
38892
+ import { basename as basename9, dirname as dirname25, join as join39, resolve as resolve32 } from "node:path";
39034
38893
  function migrateToLayout42() {
39035
38894
  const moves = [
39036
38895
  { from: legacy.authJson(), to: auditSessionFile() },
@@ -39066,7 +38925,7 @@ function migrateToLayout42() {
39066
38925
  }
39067
38926
  rmSync16(from, { force: true });
39068
38927
  }
39069
- migrated.push(`${basename8(from)} → audit/${basename8(to)}`);
38928
+ migrated.push(`${basename9(from)} → audit/${basename9(to)}`);
39070
38929
  }
39071
38930
  for (const secret of [auditSessionFile()]) {
39072
38931
  if (!existsSync46(secret))
@@ -39110,7 +38969,7 @@ function appendLedger2(entry) {
39110
38969
  } catch {}
39111
38970
  }
39112
38971
  function backupNameOf2(f) {
39113
- return f.as ?? basename8(f.at());
38972
+ return f.as ?? basename9(f.at());
39114
38973
  }
39115
38974
  function pruneMigratedCredentials2(from) {
39116
38975
  const pruned = [];
@@ -39281,7 +39140,7 @@ var init_migrations2 = __esm(() => {
39281
39140
  { at: legacy.auditSchedule }
39282
39141
  ];
39283
39142
  PRUNED_AFTER_SUCCESS2 = [
39284
- { as: basename8(legacy.authJson()), landsAt: auditSessionFile }
39143
+ { as: basename9(legacy.authJson()), landsAt: auditSessionFile }
39285
39144
  ];
39286
39145
  });
39287
39146
 
@@ -39632,6 +39491,34 @@ async function installDaemonService2() {
39632
39491
  }
39633
39492
  return { installed: true };
39634
39493
  }
39494
+ async function probeDaemon2() {
39495
+ try {
39496
+ await Promise.resolve().then(() => init_daemon_client2());
39497
+ const deadline = Date.now() + DAEMON_PROBE_READY_TIMEOUT_MS2;
39498
+ let everConnected = false;
39499
+ for (;; ) {
39500
+ if (await daemonAcceptsConnections()) {
39501
+ everConnected = true;
39502
+ const attempt = await attemptDaemonHook2({
39503
+ hookEvent: "SessionStart",
39504
+ cli: "claude",
39505
+ stdin: JSON.stringify({
39506
+ hook_event_name: "SessionStart",
39507
+ source: "failproofai-health-probe"
39508
+ })
39509
+ }, { responseTimeoutMs: DAEMON_PROBE_TIMEOUT_MS2 });
39510
+ if (attempt.ok || attempt.failure === "protocol-mismatch")
39511
+ return { ok: true };
39512
+ }
39513
+ if (Date.now() >= deadline)
39514
+ break;
39515
+ await new Promise((r) => setTimeout(r, DAEMON_PROBE_RETRY_MS2));
39516
+ }
39517
+ return { ok: false, reason: everConnected ? "worker" : "unreachable" };
39518
+ } catch {
39519
+ return { ok: false, reason: "unreachable" };
39520
+ }
39521
+ }
39635
39522
  async function daemonAcceptsConnectionsQuietly2() {
39636
39523
  try {
39637
39524
  await Promise.resolve().then(() => init_daemon_client2());
@@ -39640,6 +39527,27 @@ async function daemonAcceptsConnectionsQuietly2() {
39640
39527
  return false;
39641
39528
  }
39642
39529
  }
39530
+ async function probeDaemonEndToEnd2() {
39531
+ return (await probeDaemon2()).ok;
39532
+ }
39533
+ async function probeDaemonPolicyEvaluation2() {
39534
+ if (!await probeDaemonEndToEnd2())
39535
+ return false;
39536
+ try {
39537
+ await Promise.resolve().then(() => init_daemon_client2());
39538
+ const attempt = await attemptDaemonPolicyEvaluation({
39539
+ integration: "hermes",
39540
+ event: "on_session_start",
39541
+ payload: {
39542
+ hook_event_name: "on_session_start",
39543
+ source: "failproofai-native-policy-health-probe"
39544
+ }
39545
+ }, { responseTimeoutMs: DAEMON_PROBE_TIMEOUT_MS2 });
39546
+ return attempt.ok;
39547
+ } catch {
39548
+ return false;
39549
+ }
39550
+ }
39643
39551
  async function waitForDaemonRunning2(deps = {}) {
39644
39552
  const status = deps.status ?? daemonServiceStatus2;
39645
39553
  const accepts = deps.accepts ?? daemonAcceptsConnectionsQuietly2;
@@ -39744,7 +39652,7 @@ function daemonServiceStatus2() {
39744
39652
  return "stopped";
39745
39653
  }
39746
39654
  }
39747
- var SERVICE_CMD_TIMEOUT_MS2 = 1e4, SERVICE_START_TIMEOUT_MS2 = 5000, SERVICE_START_POLL_MS2 = 100, SERVICE_SETTLE_MS2 = 750, SUDO_PROMPT_TIMEOUT_MS2 = 120000, UNIT_UNSAFE_RE2;
39655
+ var SERVICE_CMD_TIMEOUT_MS2 = 1e4, SERVICE_START_TIMEOUT_MS2 = 5000, SERVICE_START_POLL_MS2 = 100, SERVICE_SETTLE_MS2 = 750, SUDO_PROMPT_TIMEOUT_MS2 = 120000, UNIT_UNSAFE_RE2, DAEMON_PROBE_TIMEOUT_MS2 = 5000, DAEMON_PROBE_READY_TIMEOUT_MS2 = 1e4, DAEMON_PROBE_RETRY_MS2 = 150;
39748
39656
  var init_daemon_service2 = __esm(() => {
39749
39657
  init_hook_logger();
39750
39658
  init_daemon_download();
@@ -39754,6 +39662,44 @@ var init_daemon_service2 = __esm(() => {
39754
39662
  UNIT_UNSAFE_RE2 = /["\\\r\n]/;
39755
39663
  });
39756
39664
 
39665
+ // src/hooks/hermes-update.ts
39666
+ async function runHermesUpdateMigration(deps) {
39667
+ const profiles = await migrateHermesProfiles(deps);
39668
+ const relevant = profiles.filter((p) => p.status !== "untouched");
39669
+ if (relevant.length === 0)
39670
+ return { ok: true, lines: [], profiles };
39671
+ const width = Math.max(...profiles.map((p) => ("hermes/" + p.name).length));
39672
+ const lines = ["Hermes:"];
39673
+ for (const p of profiles) {
39674
+ const label = ("hermes/" + p.name).padEnd(width);
39675
+ const detail = p.status === "current" ? "" : ` — ${p.detail}`;
39676
+ lines.push(` ${label} ${STATUS_LABEL[p.status]}${detail}`);
39677
+ }
39678
+ const blocked = profiles.filter((p) => p.status === "blocked");
39679
+ const failed = profiles.filter((p) => p.status === "failed");
39680
+ if (profiles.some((p) => p.status === "migrated")) {
39681
+ lines.push(" Cron jobs load the plugin on their next run; restart running Hermes gateways and sessions to load it there.");
39682
+ }
39683
+ if (blocked.length > 0) {
39684
+ lines.push("", `Hermes is NOT migrated (${blocked.map((p) => p.name).join(", ")}): the running failproofaid cannot`, "answer the native plugin (no policyEvaluation), so nothing was changed. Profiles still on", "shell hooks do NOT check Hermes cron jobs. Run `failproofai config` (it asks for sudo to", "replace the daemon), then `failproofai update` again.");
39685
+ }
39686
+ if (failed.length > 0) {
39687
+ lines.push("", `Hermes could not be migrated for: ${failed.map((p) => p.name).join(", ")}. Fix the reason above,`, "then run `failproofai update` again.");
39688
+ }
39689
+ return { ok: blocked.length === 0 && failed.length === 0, lines, profiles };
39690
+ }
39691
+ var STATUS_LABEL;
39692
+ var init_hermes_update = __esm(() => {
39693
+ init_integrations();
39694
+ STATUS_LABEL = {
39695
+ migrated: "migrated",
39696
+ current: "already current",
39697
+ untouched: "skipped",
39698
+ blocked: "NOT migrated",
39699
+ failed: "FAILED"
39700
+ };
39701
+ });
39702
+
39757
39703
  // src/hooks/uninstall-cli.ts
39758
39704
  import { existsSync as existsSync48, rmSync as rmSync18 } from "node:fs";
39759
39705
  function survey(cwd) {
@@ -39933,8 +39879,8 @@ var init_uninstall_cli = __esm(() => {
39933
39879
  import { execFileSync as execFileSync4 } from "node:child_process";
39934
39880
  import { homedir as homedir32, tmpdir as tmpdir4 } from "node:os";
39935
39881
  import { createHash as createHash11 } from "node:crypto";
39936
- import { copyFileSync as copyFileSync4, existsSync as existsSync49, mkdirSync as mkdirSync31, mkdtempSync as mkdtempSync3, readFileSync as readFileSync38, readdirSync as readdirSync23, realpathSync as realpathSync3, rmSync as rmSync19, statSync as statSync33, writeFileSync as writeFileSync28 } from "node:fs";
39937
- import { basename as basename9, dirname as dirname26, isAbsolute as isAbsolute6, join as join40, relative as relative5, resolve as resolve34, sep as sep6 } from "node:path";
39882
+ import { copyFileSync as copyFileSync4, existsSync as existsSync49, mkdirSync as mkdirSync31, mkdtempSync as mkdtempSync3, readFileSync as readFileSync38, readdirSync as readdirSync23, realpathSync as realpathSync4, rmSync as rmSync19, statSync as statSync33, writeFileSync as writeFileSync28 } from "node:fs";
39883
+ import { basename as basename10, dirname as dirname26, isAbsolute as isAbsolute6, join as join40, relative as relative5, resolve as resolve34, sep as sep6 } from "node:path";
39938
39884
  function parseList(rest, ...flags) {
39939
39885
  let found = false;
39940
39886
  const out = [];
@@ -40134,10 +40080,10 @@ async function build(rest) {
40134
40080
  const requiredCli = semantic.length > 0 ? minCliVersion ?? JEV_PACK_MIN_CLI : minCliVersion;
40135
40081
  const questionCost = semantic.reduce((total, entry) => total + questionChars(entry), 0);
40136
40082
  const firstParty = isFirstPartyPack(packSource);
40137
- const questionBudget = MAX_PACK_QUESTION_CHARS - (firstParty ? 0 : BUILTIN_QUESTION_CHARS);
40083
+ const questionBudget = MAX_PACK_QUESTION_CHARS - (firstParty ? 0 : JEV_POLICIES_QUESTION_CHARS);
40138
40084
  if (questionCost > questionBudget) {
40139
40085
  return fail3([
40140
- `This pack's ${semantic.length} semantic policies compile to ${questionCost} characters of questions, ` + (firstParty ? `over the ${MAX_PACK_QUESTION_CHARS} one Jev request has room for.` : `over the ${questionBudget} a machine leaves a pack from outside FailproofAI: one Jev request has room ` + `for ${MAX_PACK_QUESTION_CHARS}, and the 16 built-in checks every machine asks take ${BUILTIN_QUESTION_CHARS} of it first.`),
40086
+ `This pack's ${semantic.length} semantic policies compile to ${questionCost} characters of questions, ` + (firstParty ? `over the ${MAX_PACK_QUESTION_CHARS} one Jev request has room for.` : `over the ${questionBudget} a machine leaves a pack from outside FailproofAI: one Jev request has room ` + `for ${MAX_PACK_QUESTION_CHARS}, and FailproofAI/jev-policies' 16 checks take ${JEV_POLICIES_QUESTION_CHARS} of it first ` + `where both are installed.`),
40141
40087
  "Shorten the probe instructions and criteria, or ship fewer policies per pack."
40142
40088
  ]);
40143
40089
  }
@@ -40205,7 +40151,7 @@ ${sha(readFileSync38(entryOut))} ${PACK_ENTRY_ASSET}
40205
40151
  return ok3([
40206
40152
  `Built ${identity.id}@${identity.version} — ${policies.length} policies, ${on} on by default.`,
40207
40153
  ...semantic.length > 0 ? [
40208
- ` ${semantic.length} semantic ${semantic.length === 1 ? "policy" : "policies"} for Jev ` + `(${questionCost} characters of questions), ` + (identity.effect === "observe" ? "not asked where it installs: Jev asks only the checks of packs that enforce." : `${firstParty ? "replacing" : "added to"} the built-in checks where it installs.`)
40154
+ ` ${semantic.length} semantic ${semantic.length === 1 ? "policy" : "policies"} for Jev ` + `(${questionCost} characters of questions), ` + (identity.effect === "observe" ? "not asked where it installs: Jev asks only the checks of packs that enforce." : "asked by Jev wherever it installs.")
40209
40155
  ] : [],
40210
40156
  ...requiredCli ? [` Requires failproofai ${requiredCli} or newer.`] : [],
40211
40157
  ...semanticOnly ? JEV_ONLY_ROLLBACK_REMINDER : [],
@@ -40441,7 +40387,7 @@ function porcelainEntries(status) {
40441
40387
  }
40442
40388
  function realOrSelf(path) {
40443
40389
  try {
40444
- return realpathSync3(path);
40390
+ return realpathSync4(path);
40445
40391
  } catch {
40446
40392
  return path;
40447
40393
  }
@@ -40996,12 +40942,12 @@ async function publish(rest) {
40996
40942
  if (askingWhere) {
40997
40943
  const found = discovered.length > 0 ? discovered : [entry];
40998
40944
  process.stdout.write(`
40999
- ${found.length} policy ${found.length === 1 ? "file" : "files"}: ` + `${found.map((f) => basename9(f)).join(", ")}
40945
+ ${found.length} policy ${found.length === 1 ? "file" : "files"}: ` + `${found.map((f) => basename10(f)).join(", ")}
41000
40946
 
41001
40947
  `);
41002
40948
  const token = githubToken();
41003
40949
  const login = token ? await credentialLogin(token) : null;
41004
- const folder = basename9(resolve34(entry, ".."));
40950
+ const folder = basename10(resolve34(entry, ".."));
41005
40951
  const answered = await askWhereToPublish(`${login ?? "your-account"}/${folder}`, { stdin: process.stdin, stdout: process.stdout });
41006
40952
  if (answered === null)
41007
40953
  return ok3(["Nothing was published."]);
@@ -41012,7 +40958,7 @@ async function publish(rest) {
41012
40958
  let versionFromSha = false;
41013
40959
  const dryRun = rest.includes("--dry-run") || !repo;
41014
40960
  const allowPrivate = rest.includes("--allow-private");
41015
- const id = flag("id") ?? repo ?? (dryRun && entry ? `local/${basename9(resolve34(entry, ".."))}` : undefined);
40961
+ const id = flag("id") ?? repo ?? (dryRun && entry ? `local/${basename10(resolve34(entry, ".."))}` : undefined);
41016
40962
  const [owner, name] = (repo ?? "/").split("/");
41017
40963
  if (repo && (!owner || !name || repo.split("/").length !== 2)) {
41018
40964
  return fail3([`--repo must be <owner>/<repo>, got ${JSON.stringify(repo)}`]);
@@ -41252,11 +41198,11 @@ async function pickClis(io) {
41252
41198
  function semanticPhrase(count) {
41253
41199
  return `${count} Jev ${count === 1 ? "check" : "checks"}`;
41254
41200
  }
41255
- function besideBuiltinChecks(pack) {
41201
+ function howJevAsksChecks(pack) {
41256
41202
  if (jevPacks([{ effect: pack.effect ?? "enforce", clis: null }]).length === 0) {
41257
41203
  return "not asked: this pack only observes, and Jev asks only the checks of packs that enforce";
41258
41204
  }
41259
- const how = isFirstPartyPack(pack) ? "replacing this build's own set" : "added to this build's own checks";
41205
+ const how = "asked by Jev on every tool call they apply to";
41260
41206
  return pack.clis && pack.clis.length > 0 ? `${how}, for ${pack.clis.join(", ")} only` : how;
41261
41207
  }
41262
41208
  function jevCheckWarnings(id, preview) {
@@ -41293,7 +41239,7 @@ async function pickFromSource(source, io) {
41293
41239
  const on = preview.policies.filter((p) => p.defaultEnabled).length;
41294
41240
  if (preview.semantic.length > 0) {
41295
41241
  io.stdout.write(`
41296
- This pack also brings ${semanticPhrase(preview.semantic.length)} (not selectable), ` + `${besideBuiltinChecks(preview)}.
41242
+ This pack also brings ${semanticPhrase(preview.semantic.length)} (not selectable), ` + `${howJevAsksChecks(preview)}.
41297
41243
  See: failproofai policies show ${source}
41298
41244
 
41299
41245
  `);
@@ -41360,7 +41306,7 @@ async function add(rest) {
41360
41306
  }[result.selection];
41361
41307
  lines.push(result.enabled.length === 0 ? ` enabled (0/${result.available.length}, ${why}): none` + (result.semantic > 0 ? "" : " — the pack is installed and enforcing nothing") : ` enabled (${result.enabled.length}/${result.available.length}, ${why}): ${summarise(result.enabled)}`);
41362
41308
  if (result.semantic > 0) {
41363
- lines.push(` ${semanticPhrase(result.semantic)}, ${besideBuiltinChecks(result)}. ` + "They apply only where you configured Jev (`failproofai jev status`).");
41309
+ lines.push(` ${semanticPhrase(result.semantic)}, ${howJevAsksChecks(result)}. ` + "They apply only where you configured Jev (`failproofai jev status`).");
41364
41310
  }
41365
41311
  if (result.minCliVersionNote)
41366
41312
  lines.push(` ${result.minCliVersionNote}`);
@@ -41551,7 +41497,7 @@ function jevChecksSection(pack, opts) {
41551
41497
  ...rule(`Jev checks — ${pack.semantic.length} · not selectable · only where Jev is configured`, opts),
41552
41498
  ...table({ head: ["", "", ""], rows, protect: [0, 1] }, opts),
41553
41499
  "",
41554
- ...note("Nothing toggles them: `--policy` cannot name one and `failproofai policies` never lists them. " + `They arrive whole, ${besideBuiltinChecks(pack)}.`, opts),
41500
+ ...note("Nothing toggles them: `--policy` cannot name one and `failproofai policies` never lists them. " + `They arrive whole, ${howJevAsksChecks(pack)}.`, opts),
41555
41501
  ...note(pack.policies.length === 0 ? "This pack ships no regex policies, so none of these checks clears anything here: a deny " + "check can only ADD a deny, and an instruct check only ever warns." : `\`reviews\` is which of this pack's own ${pack.policies.length} policies each check can clear. ` + "A deny check that reviews nothing can only ever ADD a deny; an instruct check never answers " + "deny, so nothing pairs with one.", opts)
41556
41502
  ];
41557
41503
  }
@@ -41960,7 +41906,7 @@ var init_install_prompt = __esm(() => {
41960
41906
  // src/hooks/manager.ts
41961
41907
  import { execSync as execSync7 } from "node:child_process";
41962
41908
  import { existsSync as existsSync50 } from "node:fs";
41963
- import { resolve as resolve35, basename as basename10 } from "node:path";
41909
+ import { resolve as resolve35, basename as basename11 } from "node:path";
41964
41910
  import { platform as platform4, arch as arch4, release as release3, hostname as hostname6 } from "node:os";
41965
41911
  function getSettingsPath2(scope, cwd) {
41966
41912
  return claudeCode.getSettingsPath(scope, cwd);
@@ -42235,7 +42181,7 @@ Validated ${validatedHooks.length} custom hook(s) from ${path}: ${validatedHooks
42235
42181
  const alwaysOnNames = new Set(BUILTIN_POLICIES.filter((p) => p.alwaysOn).map((p) => p.name));
42236
42182
  const fromPack = policyNames === undefined ? [] : selectedPolicies.filter((name) => !alwaysOnNames.has(name));
42237
42183
  if (fromPack.length > 0) {
42238
- if (!hasInstalledPacks()) {
42184
+ if (!hasInstalledRegexPacks()) {
42239
42185
  try {
42240
42186
  await addPack(CORE_SOURCE, { only: fromPack });
42241
42187
  } catch (err) {
@@ -42621,7 +42567,7 @@ async function listHooks(cwd) {
42621
42567
  groups.push(note(dir, opts));
42622
42568
  const rows = [];
42623
42569
  for (const file of files) {
42624
- const filename = basename10(file);
42570
+ const filename = basename11(file);
42625
42571
  try {
42626
42572
  const hooks = await loadCustomHooks(file);
42627
42573
  record(filename, hooks.map((h) => h.name));
@@ -43145,13 +43091,13 @@ function parseScriptArgs(argv) {
43145
43091
  var init_parse_script_args = () => {};
43146
43092
 
43147
43093
  // scripts/install-diagnosis.mjs
43148
- import { existsSync as existsSync51, readFileSync as readFileSync40, realpathSync as realpathSync4 } from "node:fs";
43094
+ import { existsSync as existsSync51, readFileSync as readFileSync40, realpathSync as realpathSync5 } from "node:fs";
43149
43095
  import { dirname as dirname27, resolve as resolve37 } from "node:path";
43150
43096
  import { homedir as homedir33, platform as platform5 } from "node:os";
43151
43097
  import { spawnSync } from "node:child_process";
43152
43098
  function findPackageRoot(start) {
43153
43099
  try {
43154
- let dir = realpathSync4(start);
43100
+ let dir = realpathSync5(start);
43155
43101
  if (existsSync51(dir) && !existsSync51(resolve37(dir, "package.json"))) {
43156
43102
  dir = dirname27(dir);
43157
43103
  }
@@ -43231,7 +43177,7 @@ function buildRecommendation(pathFirstBin) {
43231
43177
  function diagnoseShadow(self) {
43232
43178
  const selfPackageRoot = (() => {
43233
43179
  try {
43234
- return self?.selfPackageRoot ? realpathSync4(self.selfPackageRoot) : null;
43180
+ return self?.selfPackageRoot ? realpathSync5(self.selfPackageRoot) : null;
43235
43181
  } catch {
43236
43182
  return self?.selfPackageRoot ?? null;
43237
43183
  }
@@ -43315,7 +43261,7 @@ var init_dashboard_host = __esm(() => {
43315
43261
 
43316
43262
  // scripts/launch.ts
43317
43263
  import { spawn as spawn2 } from "child_process";
43318
- import { realpathSync as realpathSync5, existsSync as existsSync52 } from "node:fs";
43264
+ import { realpathSync as realpathSync6, existsSync as existsSync52 } from "node:fs";
43319
43265
  import { resolve as resolve38, dirname as dirname28 } from "node:path";
43320
43266
  import { fileURLToPath as fileURLToPath2 } from "node:url";
43321
43267
  import { createInterface } from "node:readline";
@@ -43340,7 +43286,7 @@ function launch(mode) {
43340
43286
  process.env.PORT = port;
43341
43287
  process.env.HOSTNAME = bindHost;
43342
43288
  cmd = "node";
43343
- const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve38(dirname28(realpathSync5(fileURLToPath2(import.meta.url))), "..");
43289
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve38(dirname28(realpathSync6(fileURLToPath2(import.meta.url))), "..");
43344
43290
  const serverJsPath = resolve38(packageRoot, ".next/standalone/server.js");
43345
43291
  if (!existsSync52(serverJsPath)) {
43346
43292
  let shadowMessage = null;
@@ -44120,11 +44066,15 @@ import {
44120
44066
  existsSync as existsSync53,
44121
44067
  lstatSync as lstatSync3,
44122
44068
  mkdirSync as mkdirSync32,
44069
+ readlinkSync as readlinkSync2,
44070
+ realpathSync as realpathSync7,
44123
44071
  renameSync as renameSync13,
44124
44072
  rmSync as rmSync20,
44073
+ symlinkSync as symlinkSync2,
44125
44074
  unlinkSync as unlinkSync10
44126
44075
  } from "node:fs";
44127
- import { resolve as resolve39, dirname as dirname29 } from "node:path";
44076
+ import { resolve as resolve39, dirname as dirname29, basename as basename12 } from "node:path";
44077
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
44128
44078
  function readYamlDoc2(path) {
44129
44079
  try {
44130
44080
  return existsSync53(path) ? $parseDocument(readFileSync41(path, "utf8")) : $parseDocument("");
@@ -44141,21 +44091,71 @@ function isMarkedHook2(hook) {
44141
44091
  const cmd = typeof h.command === "string" ? h.command : "";
44142
44092
  return cmd.includes("failproofai") && cmd.includes("--hook");
44143
44093
  }
44094
+ function getHermesPluginSourcePath2() {
44095
+ const fromEnv = process.env.FAILPROOFAI_PACKAGE_ROOT;
44096
+ if (fromEnv)
44097
+ return resolve39(fromEnv, HERMES_PLUGIN_SOURCE_DIR2);
44098
+ return resolve39(fileURLToPath3(import.meta.url), "..", "..", "..", HERMES_PLUGIN_SOURCE_DIR2);
44099
+ }
44144
44100
  function hermesPluginPathForSettings2(settingsPath) {
44145
44101
  return resolve39(dirname29(settingsPath), "plugins", HERMES_PLUGIN_ID2);
44146
44102
  }
44147
- function hasManagedHermesPlugin2(settingsPath) {
44148
- const pluginPath = hermesPluginPathForSettings2(settingsPath);
44103
+ function hasAllHermesPluginFiles2(dir) {
44104
+ return HERMES_PLUGIN_FILES2.every((file) => existsSync53(resolve39(dir, file)));
44105
+ }
44106
+ function isFailproofaiHermesPluginSource2(dir) {
44107
+ if (basename12(dir) !== HERMES_PLUGIN_SOURCE_DIR2)
44108
+ return false;
44109
+ if (!existsSync53(dir))
44110
+ return true;
44149
44111
  try {
44150
- if (lstatSync3(pluginPath).isSymbolicLink())
44151
- return false;
44152
- if (!existsSync53(resolve39(pluginPath, HERMES_PLUGIN_MARKER2)))
44153
- return false;
44154
- return HERMES_PLUGIN_FILES2.every((file) => existsSync53(resolve39(pluginPath, file)));
44112
+ const manifest = readFileSync41(resolve39(dir, "plugin.yaml"), "utf8");
44113
+ return /^name:\s*["']?failproofai["']?\s*$/m.test(manifest);
44114
+ } catch {
44115
+ return false;
44116
+ }
44117
+ }
44118
+ function samePath2(a, b) {
44119
+ if (resolve39(a) === resolve39(b))
44120
+ return true;
44121
+ try {
44122
+ return realpathSync7(a) === realpathSync7(b);
44155
44123
  } catch {
44156
44124
  return false;
44157
44125
  }
44158
44126
  }
44127
+ function hermesPluginState2(settingsPath) {
44128
+ const pluginPath = hermesPluginPathForSettings2(settingsPath);
44129
+ let isLink;
44130
+ let isDirectory;
44131
+ try {
44132
+ const stat = lstatSync3(pluginPath);
44133
+ isLink = stat.isSymbolicLink();
44134
+ isDirectory = stat.isDirectory();
44135
+ } catch {
44136
+ return { kind: "absent" };
44137
+ }
44138
+ if (isLink) {
44139
+ let target;
44140
+ try {
44141
+ target = resolve39(dirname29(pluginPath), readlinkSync2(pluginPath));
44142
+ } catch {
44143
+ return { kind: "foreign" };
44144
+ }
44145
+ if (!isFailproofaiHermesPluginSource2(target))
44146
+ return { kind: "foreign" };
44147
+ return {
44148
+ kind: "link",
44149
+ target,
44150
+ complete: hasAllHermesPluginFiles2(target),
44151
+ current: samePath2(target, getHermesPluginSourcePath2())
44152
+ };
44153
+ }
44154
+ if (isDirectory && existsSync53(resolve39(pluginPath, HERMES_PLUGIN_MARKER2))) {
44155
+ return { kind: "copy", complete: hasAllHermesPluginFiles2(pluginPath) };
44156
+ }
44157
+ return { kind: "foreign" };
44158
+ }
44159
44159
  function hermesConfigState2(settingsPath) {
44160
44160
  if (!existsSync53(settingsPath)) {
44161
44161
  return { pluginEnabled: false, legacyShellHookPresent: false };
@@ -44173,20 +44173,31 @@ function hermesProfileHealth() {
44173
44173
  return listHermesProfiles().map((profile) => {
44174
44174
  const settingsPath = resolve39(profile.home, "config.yaml");
44175
44175
  const config = hermesConfigState2(settingsPath);
44176
- const pluginInstalled = hasManagedHermesPlugin2(settingsPath);
44176
+ const plugin = hermesPluginState2(settingsPath);
44177
+ const pluginInstalled = (plugin.kind === "link" || plugin.kind === "copy") && plugin.complete;
44178
+ const pluginWorks = pluginInstalled && config.pluginEnabled;
44177
44179
  return {
44178
44180
  name: profile.name,
44179
44181
  home: profile.home,
44180
44182
  settingsPath,
44181
44183
  pluginInstalled,
44184
+ pluginMode: plugin.kind === "link" || plugin.kind === "copy" ? plugin.kind : null,
44182
44185
  pluginEnabled: config.pluginEnabled,
44183
44186
  legacyShellHookPresent: config.legacyShellHookPresent,
44184
- healthy: pluginInstalled && config.pluginEnabled && !config.legacyShellHookPresent
44187
+ cronUnchecked: config.legacyShellHookPresent && !pluginWorks,
44188
+ healthy: pluginWorks && !config.legacyShellHookPresent
44185
44189
  };
44186
44190
  });
44187
44191
  }
44188
44192
  function hermesProfileStatusRows() {
44189
44193
  return hermesProfileHealth().filter((profile) => existsSync53(profile.home)).map((profile) => {
44194
+ const label = "hermes/" + profile.name;
44195
+ if (profile.cronUnchecked) {
44196
+ return [
44197
+ label,
44198
+ "UNHEALTHY — legacy shell hooks: Hermes cron jobs are not checked. Run `failproofai update`"
44199
+ ];
44200
+ }
44190
44201
  const problems = [];
44191
44202
  if (!profile.pluginInstalled)
44192
44203
  problems.push("plugin files missing or incomplete");
@@ -44194,13 +44205,15 @@ function hermesProfileStatusRows() {
44194
44205
  problems.push("plugin not enabled");
44195
44206
  if (profile.legacyShellHookPresent)
44196
44207
  problems.push("legacy shell hook also present");
44208
+ if (problems.length > 0)
44209
+ return [label, "UNHEALTHY — " + problems.join("; ")];
44197
44210
  return [
44198
- "hermes/" + profile.name,
44199
- problems.length === 0 ? "native plugin enabled" : "UNHEALTHY — " + problems.join("; ")
44211
+ label,
44212
+ profile.pluginMode === "copy" ? "native plugin enabled (copied — `failproofai update` links it so upgrades apply)" : "native plugin enabled"
44200
44213
  ];
44201
44214
  });
44202
44215
  }
44203
- var HERMES_PLUGIN_ID2 = "failproofai", HERMES_PLUGIN_MARKER2 = ".failproofai-managed", HERMES_PLUGIN_FILES2, ANTIGRAVITY_TOOL_EVENTS2;
44216
+ var HERMES_PLUGIN_ID2 = "failproofai", HERMES_PLUGIN_MARKER2 = ".failproofai-managed", HERMES_PLUGIN_FILES2, HERMES_PLUGIN_SOURCE_DIR2 = "hermes-plugin", ANTIGRAVITY_TOOL_EVENTS2;
44204
44217
  var init_integrations2 = __esm(() => {
44205
44218
  init_dist();
44206
44219
  init_hermes_profiles();
@@ -44212,9 +44225,9 @@ var init_integrations2 = __esm(() => {
44212
44225
 
44213
44226
  // scripts/launch.ts
44214
44227
  import { spawn as spawn3 } from "child_process";
44215
- import { realpathSync as realpathSync6, existsSync as existsSync54 } from "node:fs";
44228
+ import { realpathSync as realpathSync8, existsSync as existsSync54 } from "node:fs";
44216
44229
  import { resolve as resolve40, dirname as dirname30 } from "node:path";
44217
- import { fileURLToPath as fileURLToPath3 } from "node:url";
44230
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
44218
44231
  import { createInterface as createInterface2 } from "node:readline";
44219
44232
  function launch2(mode) {
44220
44233
  const { loggingLevel, disableTelemetry, allowedDevOrigins, host, remainingArgs } = parseScriptArgs(process.argv.slice(2));
@@ -44237,7 +44250,7 @@ function launch2(mode) {
44237
44250
  process.env.PORT = port;
44238
44251
  process.env.HOSTNAME = bindHost;
44239
44252
  cmd = "node";
44240
- const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve40(dirname30(realpathSync6(fileURLToPath3(import.meta.url))), "..");
44253
+ const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve40(dirname30(realpathSync8(fileURLToPath4(import.meta.url))), "..");
44241
44254
  const serverJsPath = resolve40(packageRoot, ".next/standalone/server.js");
44242
44255
  if (!existsSync54(serverJsPath)) {
44243
44256
  let shadowMessage = null;
@@ -44335,18 +44348,18 @@ var init_cli_error2 = __esm(() => {
44335
44348
  });
44336
44349
 
44337
44350
  // bin/failproofai.mjs
44338
- import { realpathSync as realpathSync7 } from "fs";
44351
+ import { realpathSync as realpathSync9 } from "fs";
44339
44352
  import { dirname as dirname31, resolve as resolve41 } from "path";
44340
- import { fileURLToPath as fileURLToPath4 } from "url";
44353
+ import { fileURLToPath as fileURLToPath5 } from "url";
44341
44354
  // package.json
44342
- var version = "1.0.8";
44355
+ var version = "1.0.9-beta.0";
44343
44356
 
44344
44357
  // bin/failproofai.mjs
44345
44358
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
44346
- process.env.FAILPROOFAI_PACKAGE_ROOT = resolve41(dirname31(realpathSync7(fileURLToPath4(import.meta.url))), "..");
44359
+ process.env.FAILPROOFAI_PACKAGE_ROOT = resolve41(dirname31(realpathSync9(fileURLToPath5(import.meta.url))), "..");
44347
44360
  }
44348
44361
  if (!process.env.FAILPROOFAI_DIST_PATH) {
44349
- process.env.FAILPROOFAI_DIST_PATH = resolve41(dirname31(realpathSync7(fileURLToPath4(import.meta.url))), "..", "dist");
44362
+ process.env.FAILPROOFAI_DIST_PATH = resolve41(dirname31(realpathSync9(fileURLToPath5(import.meta.url))), "..", "dist");
44350
44363
  }
44351
44364
  var args = process.argv.slice(2);
44352
44365
  if (args[0] === "p" || args[0] === "policy")
@@ -44920,7 +44933,7 @@ Run \`failproofai backfill --help\` for usage.`);
44920
44933
  "",
44921
44934
  "FailproofAI Cloud needs no setup here: `failproofai config --token",
44922
44935
  '<key>` with a key that carries jev:evaluate (the "machine" preset)',
44923
- "turns it on in shadow mode when there is no jev.json yet \u2014 except",
44936
+ "turns it on in observe mode when there is no jev.json yet \u2014 except",
44924
44937
  "with --no-transcripts, which only stores the key; then",
44925
44938
  "`failproofai jev setup --provider failproofai` switches it on. Its",
44926
44939
  "key stays in credentials.json; no --url ever selects it."
@@ -44937,7 +44950,7 @@ Run \`failproofai backfill --help\` for usage.`);
44937
44950
  ["--account-id <id>", "Cloudflare account id (32 hex characters)."],
44938
44951
  ["--base-url <url>", "Override the API base; `default` clears it."],
44939
44952
  ["--model <id>", "Override the model id; `default` clears it."],
44940
- ["--mode <m>", "enforce (default), shadow (log Jev, enforce regex), or off."],
44953
+ ["--mode <m>", "enforce (default), observe (log Jev, enforce regex), or off."],
44941
44954
  ["--timeout-ms <n>", "Per-call budget before falling back. Default 3000."]
44942
44955
  ]
44943
44956
  },
@@ -44948,7 +44961,7 @@ Run \`failproofai backfill --help\` for usage.`);
44948
44961
  " else can read \u2014 or one in a directory anyone else can WRITE, who",
44949
44962
  " could replace it \u2014 is refused; a repository can never set it.",
44950
44963
  "\u2022 Re-running setup for the same provider keeps the key, so",
44951
- " `failproofai jev setup --mode shadow` just switches the mode.",
44964
+ " `failproofai jev setup --mode observe` just switches the mode.",
44952
44965
  "\u2022 The daemon does not see your shell's environment: keep the key in",
44953
44966
  " the file on a machine set up with `failproofai config`.",
44954
44967
  "\u2022 --token is the fast path, not the safe one: your shell history keeps",
@@ -44962,7 +44975,7 @@ Run \`failproofai backfill --help\` for usage.`);
44962
44975
  "failproofai jev --url https://openrouter.ai/api/v1 --token <token>",
44963
44976
  "failproofai jev setup --provider typesafe --key-stdin < ~/typesafe.key",
44964
44977
  "failproofai jev setup --provider cloudflare --account-id <id> --key-stdin",
44965
- "failproofai config --token <key> (FailproofAI Cloud, shadow mode)",
44978
+ "failproofai config --token <key> (FailproofAI Cloud, observe mode)",
44966
44979
  "failproofai jev setup --mode enforce (switch the configured route's mode)",
44967
44980
  "failproofai jev setup --mode off (keep the config, stop asking Jev)",
44968
44981
  "failproofai jev test",
@@ -45107,7 +45120,10 @@ Run \`failproofai migrate --help\` for usage.`);
45107
45120
  "is the loud version of that problem rather than the silent one.",
45108
45121
  "",
45109
45122
  "This does the rest of the upgrade: runs any pending layout migrations,",
45110
- "puts the matching daemon binary in place, and restarts the service."
45123
+ "puts the matching daemon binary in place, and restarts the service.",
45124
+ "Hermes profiles already using failproofai are moved to the linked",
45125
+ "native plugin (legacy shell hooks never checked Hermes cron jobs).",
45126
+ "Exits non-zero when any half could not be brought current."
45111
45127
  ]
45112
45128
  },
45113
45129
  {
@@ -45173,17 +45189,36 @@ Run \`failproofai update --help\` for usage.`);
45173
45189
  daemonOk = result.ok;
45174
45190
  }
45175
45191
  }
45192
+ let hermesOk = true;
45193
+ let hermesMigrated = 0;
45194
+ try {
45195
+ await Promise.resolve().then(() => init_hermes_update());
45196
+ const svc = await Promise.resolve().then(() => (init_daemon_service2(), {}));
45197
+ const hermesResult = await runHermesUpdateMigration({
45198
+ daemonSupportsPolicyEvaluation: () => probeDaemonPolicyEvaluation2()
45199
+ });
45200
+ if (hermesResult.lines.length > 0)
45201
+ report.push("", ...hermesResult.lines);
45202
+ hermesOk = hermesResult.ok;
45203
+ hermesMigrated = hermesResult.profiles.filter((p) => p.status === "migrated").length;
45204
+ } catch (err) {
45205
+ report.push("", `Hermes migration failed: ${err instanceof Error ? err.message : String(err)}`);
45206
+ hermesOk = false;
45207
+ }
45208
+ const updateOk = daemonOk && !migrationFailed && hermesOk;
45176
45209
  await printReport("update", report, {
45177
- ok: daemonOk && !migrationFailed,
45210
+ ok: updateOk,
45178
45211
  meta: `v${version}`
45179
45212
  });
45180
45213
  await track("cli_update", {
45181
- ok: daemonOk && !migrationFailed,
45214
+ ok: updateOk,
45182
45215
  migrations: migrationsRan,
45183
- migration_failed: migrationFailed
45216
+ migration_failed: migrationFailed,
45217
+ hermes_ok: hermesOk,
45218
+ hermes_migrated: hermesMigrated
45184
45219
  });
45185
45220
  lastSubcommand = null;
45186
- await exitAfterFlush(daemonOk && !migrationFailed ? 0 : 1);
45221
+ await exitAfterFlush(updateOk ? 0 : 1);
45187
45222
  return;
45188
45223
  }
45189
45224
  if (args[0] === "uninstall") {