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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +3 -3
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0tmz862._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0-vcssj._.js → _0n0lja6._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1p3iz2d._.js +23 -0
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +3 -3
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/{3c3qbmosdjl6w.js → 0247hc9ath5t-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0m-9d6yn9hx4j.js → 03001k9jy-kst.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2qdpj67x6ifk_.js → 06k7gsfnb7jkh.js} +1 -1
- package/.next/standalone/.next/static/chunks/0qejem4-zt-hy.js +1 -0
- package/.next/standalone/.next/static/chunks/{21uv-uusw329x.js → 0snylg3y1i47a.js} +1 -1
- package/.next/standalone/.next/static/chunks/1h0if8ky362as.js +1 -0
- package/.next/standalone/.next/static/chunks/{0wbjy0zo-m7is.js → 2qsrrrl2maqsb.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0bhidk90e-07f.css → 3_e_wg5_oay87.css} +1 -1
- package/.next/standalone/.next/static/chunks/3ns-egfh8i8i5.js +6 -0
- package/.next/standalone/.next/static/chunks/3rq7fjj9we0v3.js +1 -0
- package/.next/standalone/.next/static/chunks/42m0f2hh8vfb1.js +1 -0
- package/.next/standalone/app/actions/get-jev-config.ts +2 -2
- package/.next/standalone/app/actions/update-jev-config.ts +12 -9
- package/.next/standalone/app/components/jev-notices.tsx +10 -10
- package/.next/standalone/app/settings/jev-panel.tsx +11 -11
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +26 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +27 -0
- package/.next/standalone/hermes-plugin/README.md +18 -8
- package/.next/standalone/package.json +10 -10
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +33 -7
- package/dist/cli.mjs +556 -521
- package/dist/worker.mjs +56 -375
- package/hermes-plugin/README.md +18 -8
- package/package.json +10 -10
- package/scripts/build-policy-pack.mjs +6 -9
- package/src/hooks/cloud-connection.ts +12 -6
- package/src/hooks/custom-hooks-loader.ts +1 -1
- package/src/hooks/effective-reviewers.ts +50 -43
- package/src/hooks/handler.ts +25 -16
- package/src/hooks/hermes-update.ts +74 -0
- package/src/hooks/hook-activity-store.ts +3 -3
- package/src/hooks/integrations.ts +349 -55
- package/src/hooks/jev-activity.ts +3 -3
- package/src/hooks/jev-cli.ts +17 -17
- package/src/hooks/jev-cloud-connection.ts +4 -4
- package/src/hooks/manager.ts +3 -2
- package/src/hooks/pack-cli.ts +24 -23
- package/src/hooks/pack-manifest.ts +25 -2
- package/src/hooks/pack-store.ts +2 -2
- package/src/hooks/policy-authority.ts +51 -32
- package/src/hooks/policy-evaluator.ts +5 -5
- package/src/hooks/policy-registry.ts +5 -7
- package/src/hooks/policy-reviewability.ts +26 -10
- package/src/hooks/policy-types.ts +4 -4
- package/src/hooks/semantic/combine.ts +13 -13
- package/src/hooks/semantic/evaluator.ts +5 -5
- package/src/hooks/semantic/jev-client.ts +1 -1
- package/src/hooks/semantic/jev-config.ts +21 -12
- package/src/hooks/semantic/jev-review.ts +3 -3
- package/src/hooks/semantic/jev-stats.ts +17 -17
- package/src/hooks/semantic/pack-policies.ts +48 -48
- package/src/hooks/semantic/policies.ts +13 -425
- package/src/hooks/semantic/precondition-names.ts +1 -1
- package/src/hooks/semantic/preconditions.ts +1 -1
- package/src/hooks/semantic/types.ts +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13t1zkw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_13kfn90._.js +0 -23
- package/.next/standalone/.next/static/chunks/06dnzbolj00mc.js +0 -6
- package/.next/standalone/.next/static/chunks/16f3fa-lx38hk.js +0 -1
- package/.next/standalone/.next/static/chunks/2g2tki08kdhie.js +0 -1
- package/.next/standalone/.next/static/chunks/3-nbtkhg9y-1j.js +0 -1
- package/.next/standalone/.next/static/chunks/355km0ihuqo1p.js +0 -1
- /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → z1C0kJVF0NCtWn5R88hW3}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → z1C0kJVF0NCtWn5R88hW3}/_clientMiddlewareManifest.js +0 -0
- /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.
|
|
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.
|
|
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.
|
|
159
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.
|
|
160
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.
|
|
161
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.
|
|
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": "
|
|
165
|
-
"linux-arm64": "
|
|
166
|
-
"darwin-x64": "
|
|
167
|
-
"darwin-arm64": "
|
|
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(["
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
3285
|
-
return
|
|
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
|
|
3292
|
-
return new Set(
|
|
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,
|
|
12922
|
-
return resolve8(fileURLToPath(import.meta.url), "..", "..", "..",
|
|
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
|
|
12928
|
-
const pluginPath = hermesPluginPathForSettings(settingsPath);
|
|
12950
|
+
function pathPresent(path) {
|
|
12929
12951
|
try {
|
|
12930
|
-
|
|
12931
|
-
|
|
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
|
|
12940
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12968
|
-
|
|
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
|
-
|
|
12971
|
-
|
|
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 && !
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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 =
|
|
16771
|
+
const policies = [];
|
|
16923
16772
|
const errors = [];
|
|
16924
|
-
const seen = new Set
|
|
16773
|
+
const seen = new Set;
|
|
16925
16774
|
const contested = contestedSemanticNames(declared);
|
|
16926
|
-
let spent =
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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(`${
|
|
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:
|
|
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,
|
|
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:
|
|
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",
|
|
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:
|
|
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 === "
|
|
19322
|
-
const
|
|
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, ...
|
|
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 === "
|
|
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.
|
|
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
|
|
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, `${
|
|
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
|
-
|
|
27292
|
-
|
|
27293
|
-
|
|
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
|
|
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 === "
|
|
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 === "
|
|
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 === "
|
|
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 =
|
|
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
|
|
30302
|
-
if (
|
|
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:
|
|
30308
|
-
reason:
|
|
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
|
|
30399
|
-
if (
|
|
30252
|
+
const observeVerdict = result.twoTier?.observeVerdict;
|
|
30253
|
+
if (observeVerdict) {
|
|
30400
30254
|
observedResults.push({
|
|
30401
|
-
policyId:
|
|
30402
|
-
version:
|
|
30403
|
-
decision:
|
|
30404
|
-
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
|
|
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(`${
|
|
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 ??
|
|
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:
|
|
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
|
|
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 (!
|
|
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 = "
|
|
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
|
|
33138
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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: {
|
|
37674
|
-
|
|
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
|
|
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 === "
|
|
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.
|
|
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" : "
|
|
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
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
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|
|
|
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
|
|
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(`${
|
|
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 ??
|
|
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:
|
|
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
|
|
39937
|
-
import { basename as
|
|
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 :
|
|
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
|
|
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." :
|
|
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
|
|
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) =>
|
|
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 =
|
|
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/${
|
|
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
|
|
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 =
|
|
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), ` + `${
|
|
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)}, ${
|
|
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, ${
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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 =
|
|
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 ?
|
|
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
|
|
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(
|
|
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
|
|
44148
|
-
|
|
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
|
-
|
|
44151
|
-
|
|
44152
|
-
|
|
44153
|
-
|
|
44154
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
44199
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
44351
|
+
import { realpathSync as realpathSync9 } from "fs";
|
|
44339
44352
|
import { dirname as dirname31, resolve as resolve41 } from "path";
|
|
44340
|
-
import { fileURLToPath as
|
|
44353
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
44341
44354
|
// package.json
|
|
44342
|
-
var version = "1.0.
|
|
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(
|
|
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(
|
|
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
|
|
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),
|
|
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
|
|
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,
|
|
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:
|
|
45210
|
+
ok: updateOk,
|
|
45178
45211
|
meta: `v${version}`
|
|
45179
45212
|
});
|
|
45180
45213
|
await track("cli_update", {
|
|
45181
|
-
ok:
|
|
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(
|
|
45221
|
+
await exitAfterFlush(updateOk ? 0 : 1);
|
|
45187
45222
|
return;
|
|
45188
45223
|
}
|
|
45189
45224
|
if (args[0] === "uninstall") {
|