failproofai 1.0.8-beta.0 → 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 +3 -3
- 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]__14o3ek1._.js → [root-of-the-server]__0tmz862._.js} +2 -2
- 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/{_0o4xkpl._.js → _0n0lja6._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1-7sqrb._.js → _1p3iz2d._.js} +2 -2
- 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/node_modules_next_0aiy-os._.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/{2z7i-yg59w4pn.js → 0247hc9ath5t-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ahfwmbkpgfw4.js → 03001k9jy-kst.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2vo7qbdbvnc0o.js → 06k7gsfnb7jkh.js} +1 -1
- package/.next/standalone/.next/static/chunks/0qejem4-zt-hy.js +1 -0
- package/.next/standalone/.next/static/chunks/{1v_tp3hm8wyhe.js → 0snylg3y1i47a.js} +1 -1
- package/.next/standalone/.next/static/chunks/1h0if8ky362as.js +1 -0
- package/.next/standalone/.next/static/chunks/{3bgot5v6f6s3c.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/sdk/typescript/CHANGELOG.md +18 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +0 -274
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +33 -7
- package/dist/cli.mjs +664 -553
- package/dist/worker.mjs +164 -407
- 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/decide.ts +134 -29
- package/src/hooks/semantic/evaluator.ts +5 -5
- package/src/hooks/semantic/facts.ts +91 -2
- 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 +7 -1
- package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +0 -1
- package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +0 -1
- package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +0 -1
- package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +0 -1
- package/.next/standalone/.next/static/chunks/3o3f1ibfci0p7.js +0 -6
- /package/.next/standalone/.next/static/{O_b5R1axf5cb4NxIXq2kS → z1C0kJVF0NCtWn5R88hW3}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{O_b5R1axf5cb4NxIXq2kS → z1C0kJVF0NCtWn5R88hW3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{O_b5R1axf5cb4NxIXq2kS → 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;
|
|
12966
|
+
try {
|
|
12967
|
+
const manifest = readFileSync5(resolve8(dir, "plugin.yaml"), "utf8");
|
|
12968
|
+
return /^name:\s*["']?failproofai["']?\s*$/m.test(manifest);
|
|
12969
|
+
} catch {
|
|
12970
|
+
return false;
|
|
12971
|
+
}
|
|
12972
|
+
}
|
|
12973
|
+
function samePath(a, b) {
|
|
12974
|
+
if (resolve8(a) === resolve8(b))
|
|
12975
|
+
return true;
|
|
12941
12976
|
try {
|
|
12942
|
-
return
|
|
12977
|
+
return realpathSync2(a) === realpathSync2(b);
|
|
12943
12978
|
} catch {
|
|
12944
12979
|
return false;
|
|
12945
12980
|
}
|
|
12946
12981
|
}
|
|
12947
|
-
function
|
|
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 };
|
|
@@ -27570,6 +27420,19 @@ function classifyTool(toolName) {
|
|
|
27570
27420
|
return { toolClass: "other", toolIsKnown: true };
|
|
27571
27421
|
return { toolClass: "other", toolIsKnown: false };
|
|
27572
27422
|
}
|
|
27423
|
+
function runsNestedShell(tokens) {
|
|
27424
|
+
let shellSeen = false;
|
|
27425
|
+
for (const tok of tokens) {
|
|
27426
|
+
if (shellSeen && /^-[a-z]*c[a-z]*$/i.test(tok))
|
|
27427
|
+
return true;
|
|
27428
|
+
const base = tok.slice(tok.lastIndexOf("/") + 1);
|
|
27429
|
+
if (base === "eval")
|
|
27430
|
+
return true;
|
|
27431
|
+
if (NESTED_SHELLS.has(base))
|
|
27432
|
+
shellSeen = true;
|
|
27433
|
+
}
|
|
27434
|
+
return false;
|
|
27435
|
+
}
|
|
27573
27436
|
function scanCommand(command) {
|
|
27574
27437
|
const text = command.length > MAX_SCAN_CHARS ? command.slice(0, MAX_SCAN_CHARS) : command;
|
|
27575
27438
|
const segments = [];
|
|
@@ -27580,22 +27443,36 @@ function scanCommand(command) {
|
|
|
27580
27443
|
let out = "";
|
|
27581
27444
|
let commentsRemoved = false;
|
|
27582
27445
|
const comments = [];
|
|
27446
|
+
let complete = command.length <= MAX_SCAN_CHARS;
|
|
27447
|
+
let braceDepth = 0;
|
|
27448
|
+
let braceSep = false;
|
|
27449
|
+
let bracketOpen = false;
|
|
27583
27450
|
const endWord = () => {
|
|
27584
27451
|
if (inWord)
|
|
27585
27452
|
tokens.push(word);
|
|
27586
27453
|
word = "";
|
|
27587
27454
|
inWord = false;
|
|
27455
|
+
braceDepth = 0;
|
|
27456
|
+
braceSep = false;
|
|
27457
|
+
bracketOpen = false;
|
|
27588
27458
|
};
|
|
27589
27459
|
const endSegment = () => {
|
|
27590
27460
|
endWord();
|
|
27591
|
-
if (tokens.length > 0)
|
|
27461
|
+
if (tokens.length > 0) {
|
|
27462
|
+
if (complete && runsNestedShell(tokens))
|
|
27463
|
+
complete = false;
|
|
27592
27464
|
segments.push(tokens);
|
|
27465
|
+
}
|
|
27593
27466
|
tokens = [];
|
|
27594
27467
|
};
|
|
27595
27468
|
for (let i = 0;i < text.length; i++) {
|
|
27596
27469
|
const c = text[i];
|
|
27597
27470
|
if (quote) {
|
|
27598
27471
|
out += c;
|
|
27472
|
+
if (quote === '"' && (c === "`" || c === "$" && EXPANSION_START.test(text[i + 1] ?? "") || c === "\\" && text[i + 1] === `
|
|
27473
|
+
`)) {
|
|
27474
|
+
complete = false;
|
|
27475
|
+
}
|
|
27599
27476
|
if (c === quote) {
|
|
27600
27477
|
quote = null;
|
|
27601
27478
|
} else if (c === "\\" && quote === '"' && i + 1 < text.length) {
|
|
@@ -27606,6 +27483,10 @@ function scanCommand(command) {
|
|
|
27606
27483
|
}
|
|
27607
27484
|
continue;
|
|
27608
27485
|
}
|
|
27486
|
+
if (c === "`" || c === "$" && (text[i + 1] === "'" || text[i + 1] === '"' || EXPANSION_START.test(text[i + 1] ?? "")) || (c === "<" || c === ">") && text[i + 1] === "(" || c === "<" && text[i + 1] === "<" || c === "\\" && text[i + 1] === `
|
|
27487
|
+
`) {
|
|
27488
|
+
complete = false;
|
|
27489
|
+
}
|
|
27609
27490
|
if (c === "'" || c === '"') {
|
|
27610
27491
|
quote = c;
|
|
27611
27492
|
inWord = true;
|
|
@@ -27646,12 +27527,29 @@ function scanCommand(command) {
|
|
|
27646
27527
|
out += c;
|
|
27647
27528
|
continue;
|
|
27648
27529
|
}
|
|
27530
|
+
if (c === "{") {
|
|
27531
|
+
braceDepth++;
|
|
27532
|
+
} else if (braceDepth > 0 && (c === "," || c === "." && text[i + 1] === ".")) {
|
|
27533
|
+
braceSep = true;
|
|
27534
|
+
} else if (c === "}" && braceDepth > 0) {
|
|
27535
|
+
braceDepth--;
|
|
27536
|
+
if (braceSep)
|
|
27537
|
+
complete = false;
|
|
27538
|
+
} else if (c === "*" || c === "?") {
|
|
27539
|
+
complete = false;
|
|
27540
|
+
} else if (c === "[") {
|
|
27541
|
+
bracketOpen = true;
|
|
27542
|
+
} else if (c === "]" && bracketOpen) {
|
|
27543
|
+
complete = false;
|
|
27544
|
+
}
|
|
27649
27545
|
word += c;
|
|
27650
27546
|
inWord = true;
|
|
27651
27547
|
out += c;
|
|
27652
27548
|
}
|
|
27653
27549
|
endSegment();
|
|
27654
|
-
|
|
27550
|
+
if (quote)
|
|
27551
|
+
complete = false;
|
|
27552
|
+
return { segments, withoutComments: out.trimEnd(), commentsRemoved, comments, complete };
|
|
27655
27553
|
}
|
|
27656
27554
|
function expandHome(token, home) {
|
|
27657
27555
|
if (token === "~")
|
|
@@ -27784,7 +27682,7 @@ function computeFacts(toolName, toolInput, cwd, permissionMode, scanned, pinnedP
|
|
|
27784
27682
|
permissionMode
|
|
27785
27683
|
};
|
|
27786
27684
|
}
|
|
27787
|
-
var SHELL_TOOLS, WRITE_TOOLS, READ_TOOLS, NETWORK_TOOLS, INERT_TOOLS, MAX_SCAN_CHARS = 8192, PATH_LIKE, GLOB_CHARS, MAX_PATHS = 12;
|
|
27685
|
+
var SHELL_TOOLS, WRITE_TOOLS, READ_TOOLS, NETWORK_TOOLS, INERT_TOOLS, MAX_SCAN_CHARS = 8192, NESTED_SHELLS, EXPANSION_START, PATH_LIKE, GLOB_CHARS, MAX_PATHS = 12;
|
|
27788
27686
|
var init_facts = __esm(() => {
|
|
27789
27687
|
SHELL_TOOLS = new Set(["Bash", "BashOutput"]);
|
|
27790
27688
|
WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
@@ -27803,6 +27701,8 @@ var init_facts = __esm(() => {
|
|
|
27803
27701
|
"SlashCommand",
|
|
27804
27702
|
"Skill"
|
|
27805
27703
|
]);
|
|
27704
|
+
NESTED_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "fish"]);
|
|
27705
|
+
EXPANSION_START = /[A-Za-z_0-9@*#?$!\-({]/;
|
|
27806
27706
|
PATH_LIKE = /^(?:\/|~|\.\.?(?:\/|$)|[^:]*\/)/;
|
|
27807
27707
|
GLOB_CHARS = /[*?[]/;
|
|
27808
27708
|
});
|
|
@@ -27811,50 +27711,70 @@ var init_facts = __esm(() => {
|
|
|
27811
27711
|
function tokensOf(text) {
|
|
27812
27712
|
return text.toLowerCase().split(/[^a-z0-9._@-]+/).filter((t) => t.length >= 3 && !GENERIC_TOKENS.has(t) && !/^\d+$/.test(t));
|
|
27813
27713
|
}
|
|
27814
|
-
function
|
|
27815
|
-
const
|
|
27714
|
+
function scanTargets(toolInput) {
|
|
27715
|
+
const groups = [];
|
|
27716
|
+
const addGroup = (tok) => {
|
|
27717
|
+
const words = tokensOf(tok);
|
|
27718
|
+
if (words.length > 0)
|
|
27719
|
+
groups.push(new Set(words));
|
|
27720
|
+
};
|
|
27816
27721
|
const command = typeof toolInput.command === "string" ? toolInput.command : null;
|
|
27817
27722
|
if (command) {
|
|
27818
|
-
|
|
27723
|
+
const scanned = scanCommand(command);
|
|
27724
|
+
for (const seg of scanned.segments) {
|
|
27819
27725
|
for (const tok of seg.slice(1)) {
|
|
27820
|
-
if (tok.startsWith("-"))
|
|
27821
|
-
|
|
27822
|
-
for (const t of tokensOf(tok))
|
|
27823
|
-
out.add(t);
|
|
27726
|
+
if (!tok.startsWith("-"))
|
|
27727
|
+
addGroup(tok);
|
|
27824
27728
|
}
|
|
27825
27729
|
}
|
|
27826
|
-
if (
|
|
27730
|
+
if (groups.length === 0) {
|
|
27827
27731
|
for (const piece of command.split(/[;&|\n]+/)) {
|
|
27828
27732
|
for (const tok of piece.trim().split(/\s+/).slice(1)) {
|
|
27829
27733
|
if (!tok.startsWith("-"))
|
|
27830
|
-
|
|
27831
|
-
out.add(t);
|
|
27734
|
+
addGroup(tok);
|
|
27832
27735
|
}
|
|
27833
27736
|
}
|
|
27834
27737
|
}
|
|
27835
|
-
return
|
|
27738
|
+
return { targets: new Set(groups.flatMap((g) => [...g])), groups, complete: scanned.complete };
|
|
27836
27739
|
}
|
|
27740
|
+
const all = new Set;
|
|
27837
27741
|
for (const [key, value] of Object.entries(toolInput)) {
|
|
27838
27742
|
if (typeof value !== "string" || value.length > 300)
|
|
27839
27743
|
continue;
|
|
27840
27744
|
if (/content|old_string|new_string|body|text|prompt/i.test(key))
|
|
27841
27745
|
continue;
|
|
27842
27746
|
for (const t of tokensOf(value))
|
|
27843
|
-
|
|
27747
|
+
all.add(t);
|
|
27844
27748
|
}
|
|
27845
|
-
return
|
|
27749
|
+
return { targets: all, groups: all.size > 0 ? [all] : [], complete: true };
|
|
27846
27750
|
}
|
|
27847
|
-
function
|
|
27848
|
-
if (userSaid.length === 0)
|
|
27751
|
+
function everyTargetNamed(scan, userSaid) {
|
|
27752
|
+
if (userSaid.length === 0 || scan.groups.length === 0)
|
|
27849
27753
|
return false;
|
|
27850
|
-
|
|
27754
|
+
const said = userSaid.join(`
|
|
27755
|
+
`).toLowerCase();
|
|
27756
|
+
return scan.groups.every((g) => {
|
|
27757
|
+
for (const t of g)
|
|
27758
|
+
if (said.includes(t))
|
|
27759
|
+
return true;
|
|
27760
|
+
return false;
|
|
27761
|
+
});
|
|
27762
|
+
}
|
|
27763
|
+
function partlyNamed(scan, userSaid) {
|
|
27764
|
+
if (userSaid.length === 0 || scan.groups.length < 2)
|
|
27851
27765
|
return false;
|
|
27852
27766
|
const said = userSaid.join(`
|
|
27853
27767
|
`).toLowerCase();
|
|
27854
|
-
|
|
27855
|
-
|
|
27856
|
-
|
|
27857
|
-
|
|
27768
|
+
let named = 0;
|
|
27769
|
+
for (const g of scan.groups) {
|
|
27770
|
+
for (const t of g) {
|
|
27771
|
+
if (said.includes(t)) {
|
|
27772
|
+
named++;
|
|
27773
|
+
break;
|
|
27774
|
+
}
|
|
27775
|
+
}
|
|
27776
|
+
}
|
|
27777
|
+
return named > 0 && named < scan.groups.length;
|
|
27858
27778
|
}
|
|
27859
27779
|
function formatOutcome(p, o) {
|
|
27860
27780
|
return `${p.title} (semantic/${p.name}, p=${o.evidence.toFixed(2)}). ${p.guidance}`;
|
|
@@ -27864,7 +27784,7 @@ function decide(selected, answers, toolInput, userSaid, thresholds = DEFAULT_THR
|
|
|
27864
27784
|
const injected = injection !== null && injection >= thresholds.injection;
|
|
27865
27785
|
const scope = typeof answers.scope === "number" ? answers.scope : null;
|
|
27866
27786
|
const withinScope = scope !== null && scope >= thresholds.scope;
|
|
27867
|
-
let
|
|
27787
|
+
let scan = null;
|
|
27868
27788
|
const outcomes = selected.map((p) => {
|
|
27869
27789
|
const evidence = Math.min(...p.probes.map((probe) => answers[`${p.name}.${probe.id}`] ?? 0));
|
|
27870
27790
|
const exempt = p.exempt ? answers[`${p.name}.exempt`] ?? 0 : null;
|
|
@@ -27887,9 +27807,11 @@ function decide(selected, answers, toolInput, userSaid, thresholds = DEFAULT_THR
|
|
|
27887
27807
|
return { ...base, escalatedByInjection: true, verdict: "deny" };
|
|
27888
27808
|
const fired = p.mode === "deny" && evidence >= thresholds.deny ? "deny" : "instruct";
|
|
27889
27809
|
if (p.userCanOverride && userAsked !== null && userAsked >= thresholds.userAsked && withinScope) {
|
|
27890
|
-
|
|
27891
|
-
|
|
27892
|
-
|
|
27810
|
+
scan ??= scanTargets(toolInput);
|
|
27811
|
+
if (!scan.complete)
|
|
27812
|
+
return { ...base, verdict: fired, targetScanIncomplete: true };
|
|
27813
|
+
const named = scan.groups.length > 0 && everyTargetNamed(scan, userSaid);
|
|
27814
|
+
if (scan.groups.length === 0 || named || userSaidCut)
|
|
27893
27815
|
return { ...base, targetNamedByUser: named, verdict: "overridden" };
|
|
27894
27816
|
}
|
|
27895
27817
|
return { ...base, verdict: fired };
|
|
@@ -27939,12 +27861,14 @@ function decideV1(selected, answers, toolInput, userSaid, agentLastMessage, opts
|
|
|
27939
27861
|
const task = num("task_step");
|
|
27940
27862
|
const op = num("op_requested");
|
|
27941
27863
|
const beyond = num("beyond_task");
|
|
27942
|
-
let
|
|
27864
|
+
let scan = null;
|
|
27865
|
+
const targetScan = () => scan ??= scanTargets(toolInput);
|
|
27866
|
+
const evidenceSaid = agentLastMessage ? [...userSaid, agentLastMessage] : userSaid;
|
|
27943
27867
|
const targetOk = () => {
|
|
27944
|
-
|
|
27945
|
-
if (
|
|
27868
|
+
const s = targetScan();
|
|
27869
|
+
if (s.groups.length === 0)
|
|
27946
27870
|
return { ok: true, named: false };
|
|
27947
|
-
const named =
|
|
27871
|
+
const named = everyTargetNamed(s, evidenceSaid);
|
|
27948
27872
|
return { ok: named || userSaidCut, named };
|
|
27949
27873
|
};
|
|
27950
27874
|
const outcomes = selected.map((p) => {
|
|
@@ -27969,12 +27893,14 @@ function decideV1(selected, answers, toolInput, userSaid, agentLastMessage, opts
|
|
|
27969
27893
|
const fired = p.mode === "deny" && evidence >= t.deny ? "deny" : "instruct";
|
|
27970
27894
|
if (!p.userCanOverride)
|
|
27971
27895
|
return { ...base, verdict: fired };
|
|
27896
|
+
if (!targetScan().complete)
|
|
27897
|
+
return { ...base, verdict: fired, targetScanIncomplete: true };
|
|
27972
27898
|
if (op !== null && op >= t.opRequested && beyond !== null && beyond < t.opBeyondMax) {
|
|
27973
27899
|
const target = targetOk();
|
|
27974
27900
|
if (target.ok)
|
|
27975
27901
|
return { ...base, targetNamedByUser: target.named, verdict: "overridden", intent: "op-requested" };
|
|
27976
27902
|
}
|
|
27977
|
-
if (taskClears && task !== null && task >= t.taskStep && beyond !== null && beyond < t.taskBeyondMax) {
|
|
27903
|
+
if (taskClears && task !== null && task >= t.taskStep && beyond !== null && beyond < t.taskBeyondMax && !(typeof toolInput.command === "string" && !userSaidCut && partlyNamed(targetScan(), evidenceSaid))) {
|
|
27978
27904
|
if (fired === "instruct")
|
|
27979
27905
|
return { ...base, verdict: "overridden", intent: "task-step" };
|
|
27980
27906
|
return { ...base, verdict: "instruct", intent: "downgraded-task-step" };
|
|
@@ -29657,7 +29583,7 @@ function authorityOf(p) {
|
|
|
29657
29583
|
return { authority, reviewedBy };
|
|
29658
29584
|
}
|
|
29659
29585
|
function resolveMode(cfg) {
|
|
29660
|
-
return cfg.mode === "
|
|
29586
|
+
return cfg.mode === "observe" || cfg.mode === "enforce" ? cfg.mode : DEFAULT_JEV_MODE;
|
|
29661
29587
|
}
|
|
29662
29588
|
function resolveTimeout(cfg) {
|
|
29663
29589
|
const t = cfg.timeoutMs;
|
|
@@ -29775,7 +29701,7 @@ function startJevReview(cfg, call) {
|
|
|
29775
29701
|
sessionId: call.sessionId,
|
|
29776
29702
|
cli: call.cli,
|
|
29777
29703
|
eventType: call.eventType,
|
|
29778
|
-
applied: abandoned || review.kind !== "answered" ? "legacy-fallback" : mode === "
|
|
29704
|
+
applied: abandoned || review.kind !== "answered" ? "legacy-fallback" : mode === "observe" ? "observe" : "two-tier"
|
|
29779
29705
|
});
|
|
29780
29706
|
appendVerdictLog(hit ? { ...row, cached: true } : row);
|
|
29781
29707
|
}
|
|
@@ -30026,6 +29952,8 @@ async function startTwoTier(canonicalEventType, parsed, session, cli, opts, acti
|
|
|
30026
29952
|
return null;
|
|
30027
29953
|
if (jevForcedOff(opts) || activePause)
|
|
30028
29954
|
return null;
|
|
29955
|
+
if (!jevChecksInstalled())
|
|
29956
|
+
return null;
|
|
30029
29957
|
const loaded = await readJevConfig();
|
|
30030
29958
|
if (!loaded)
|
|
30031
29959
|
return null;
|
|
@@ -30044,7 +29972,7 @@ async function startTwoTier(canonicalEventType, parsed, session, cli, opts, acti
|
|
|
30044
29972
|
} catch (err) {
|
|
30045
29973
|
hookLogInfo(`Jev review could not start (${err instanceof Error ? err.message : String(err)})`);
|
|
30046
29974
|
return {
|
|
30047
|
-
mode: cfg.mode === "
|
|
29975
|
+
mode: cfg.mode === "observe" || cfg.mode === "enforce" ? cfg.mode : loaded.defaultMode,
|
|
30048
29976
|
review: Promise.resolve({ kind: "fallback", reason: "error", latencyMs: null, model: null }),
|
|
30049
29977
|
abort: () => {},
|
|
30050
29978
|
authorityOf: () => ({ authority: "hard", reviewedBy: [] })
|
|
@@ -30068,6 +29996,8 @@ async function captureJevIntent(canonicalEventType, decision, parsed, session, c
|
|
|
30068
29996
|
if (canonicalEventType !== "UserPromptSubmit" || jevForcedOff(opts))
|
|
30069
29997
|
return;
|
|
30070
29998
|
try {
|
|
29999
|
+
if (!jevChecksInstalled())
|
|
30000
|
+
return;
|
|
30071
30001
|
if (!await readJevConfig())
|
|
30072
30002
|
return;
|
|
30073
30003
|
if (decision === "deny") {
|
|
@@ -30148,7 +30078,7 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
30148
30078
|
config = readMergedHooksConfig(session.cwd);
|
|
30149
30079
|
clearPolicies(cli);
|
|
30150
30080
|
activePause = readActivePause(session.sessionId);
|
|
30151
|
-
const packsInstalledHere =
|
|
30081
|
+
const packsInstalledHere = hasInstalledRegexPacks();
|
|
30152
30082
|
const legacyNames = activePause || packsInstalledHere ? [] : config.enabledPolicies;
|
|
30153
30083
|
registerBuiltinPolicies(activePause ? [] : legacyNames);
|
|
30154
30084
|
if (legacyNames.length > 0) {
|
|
@@ -30222,14 +30152,14 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
30222
30152
|
const observeOnly = cloudManaged?.effect === "observe" || pack?.effect === "observe";
|
|
30223
30153
|
const fn = async (ctx) => {
|
|
30224
30154
|
if (observeOnly) {
|
|
30225
|
-
const
|
|
30226
|
-
if (
|
|
30155
|
+
const observed = await runObserved(hook, ctx, hookName, eventType, cli);
|
|
30156
|
+
if (observed.decision !== "allow") {
|
|
30227
30157
|
const observer = cloudManaged ?? pack;
|
|
30228
30158
|
observedResults.push({
|
|
30229
30159
|
policyId: observer.id,
|
|
30230
30160
|
version: observer.version,
|
|
30231
|
-
decision:
|
|
30232
|
-
reason:
|
|
30161
|
+
decision: observed.decision,
|
|
30162
|
+
reason: observed.reason ?? null
|
|
30233
30163
|
});
|
|
30234
30164
|
}
|
|
30235
30165
|
return { decision: "allow" };
|
|
@@ -30319,13 +30249,13 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
30319
30249
|
...twoTier,
|
|
30320
30250
|
releaseRegistry: opts?.releaseRegistry
|
|
30321
30251
|
}) : await evaluatePolicies(canonicalEventType, parsed, session, config);
|
|
30322
|
-
const
|
|
30323
|
-
if (
|
|
30252
|
+
const observeVerdict = result.twoTier?.observeVerdict;
|
|
30253
|
+
if (observeVerdict) {
|
|
30324
30254
|
observedResults.push({
|
|
30325
|
-
policyId:
|
|
30326
|
-
version:
|
|
30327
|
-
decision:
|
|
30328
|
-
reason:
|
|
30255
|
+
policyId: observeVerdict.policyName,
|
|
30256
|
+
version: observeVerdict.version,
|
|
30257
|
+
decision: observeVerdict.decision,
|
|
30258
|
+
reason: observeVerdict.reason
|
|
30329
30259
|
});
|
|
30330
30260
|
}
|
|
30331
30261
|
const durationMs = Math.round(performance.now() - startTime);
|
|
@@ -31248,7 +31178,7 @@ import {
|
|
|
31248
31178
|
rmSync as rmSync9,
|
|
31249
31179
|
writeFileSync as writeFileSync14
|
|
31250
31180
|
} from "node:fs";
|
|
31251
|
-
import { basename as
|
|
31181
|
+
import { basename as basename6, dirname as dirname16, join as join32, resolve as resolve24 } from "node:path";
|
|
31252
31182
|
function migrateToLayout4() {
|
|
31253
31183
|
const moves = [
|
|
31254
31184
|
{ from: legacy.authJson(), to: auditSessionFile() },
|
|
@@ -31284,7 +31214,7 @@ function migrateToLayout4() {
|
|
|
31284
31214
|
}
|
|
31285
31215
|
rmSync9(from, { force: true });
|
|
31286
31216
|
}
|
|
31287
|
-
migrated.push(`${
|
|
31217
|
+
migrated.push(`${basename6(from)} → audit/${basename6(to)}`);
|
|
31288
31218
|
}
|
|
31289
31219
|
for (const secret of [auditSessionFile()]) {
|
|
31290
31220
|
if (!existsSync29(secret))
|
|
@@ -31328,7 +31258,7 @@ function appendLedger(entry) {
|
|
|
31328
31258
|
} catch {}
|
|
31329
31259
|
}
|
|
31330
31260
|
function backupNameOf(f) {
|
|
31331
|
-
return f.as ??
|
|
31261
|
+
return f.as ?? basename6(f.at());
|
|
31332
31262
|
}
|
|
31333
31263
|
function pruneMigratedCredentials(from) {
|
|
31334
31264
|
const pruned = [];
|
|
@@ -31477,7 +31407,7 @@ var init_migrations = __esm(() => {
|
|
|
31477
31407
|
{ at: legacy.auditSchedule }
|
|
31478
31408
|
];
|
|
31479
31409
|
PRUNED_AFTER_SUCCESS = [
|
|
31480
|
-
{ as:
|
|
31410
|
+
{ as: basename6(legacy.authJson()), landsAt: auditSessionFile }
|
|
31481
31411
|
];
|
|
31482
31412
|
});
|
|
31483
31413
|
|
|
@@ -31929,7 +31859,7 @@ var init_cli_error = __esm(() => {
|
|
|
31929
31859
|
// src/hooks/manager.ts
|
|
31930
31860
|
import { execSync as execSync6 } from "node:child_process";
|
|
31931
31861
|
import { existsSync as existsSync31 } from "node:fs";
|
|
31932
|
-
import { resolve as resolve26, basename as
|
|
31862
|
+
import { resolve as resolve26, basename as basename7 } from "node:path";
|
|
31933
31863
|
import { platform as platform3, arch as arch3, release as release2, hostname as hostname3 } from "node:os";
|
|
31934
31864
|
function getSettingsPath(scope, cwd) {
|
|
31935
31865
|
return claudeCode.getSettingsPath(scope, cwd);
|
|
@@ -32204,7 +32134,7 @@ Validated ${validatedHooks.length} custom hook(s) from ${path}: ${validatedHooks
|
|
|
32204
32134
|
const alwaysOnNames = new Set(BUILTIN_POLICIES.filter((p) => p.alwaysOn).map((p) => p.name));
|
|
32205
32135
|
const fromPack = policyNames === undefined ? [] : selectedPolicies.filter((name) => !alwaysOnNames.has(name));
|
|
32206
32136
|
if (fromPack.length > 0) {
|
|
32207
|
-
if (!
|
|
32137
|
+
if (!hasInstalledRegexPacks()) {
|
|
32208
32138
|
try {
|
|
32209
32139
|
await addPack(CORE_SOURCE, { only: fromPack });
|
|
32210
32140
|
} catch (err) {
|
|
@@ -32923,7 +32853,7 @@ function fieldsOf(file) {
|
|
|
32923
32853
|
} catch {}
|
|
32924
32854
|
}
|
|
32925
32855
|
}
|
|
32926
|
-
var CLOUD_JEV_INITIAL_MODE = "
|
|
32856
|
+
var CLOUD_JEV_INITIAL_MODE = "observe", MAX_JEV_CONFIG_BYTES;
|
|
32927
32857
|
var init_jev_cloud_connection = __esm(() => {
|
|
32928
32858
|
init_jev_config();
|
|
32929
32859
|
MAX_JEV_CONFIG_BYTES = 64 * 1024;
|
|
@@ -33058,18 +32988,19 @@ function jevLines(outcome) {
|
|
|
33058
32988
|
if (!config || config.status === "written") {
|
|
33059
32989
|
const plainHttp = config?.status === "written" && new URL(config.baseUrl).protocol === "http:";
|
|
33060
32990
|
return [
|
|
33061
|
-
` Jev on through FailproofAI Cloud, in
|
|
33062
|
-
|
|
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."
|
|
33063
32994
|
];
|
|
33064
32995
|
}
|
|
33065
32996
|
if (config.status === "kept") {
|
|
33066
32997
|
const lines = [` Jev key stored; ${config.path} already exists and was left as configured.`];
|
|
33067
32998
|
if (config.otherOrigin) {
|
|
33068
|
-
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\`.`);
|
|
33069
33000
|
} else if (config.jevOff?.why === "refused") {
|
|
33070
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`."}`);
|
|
33071
33002
|
} else if (config.jevOff?.why === "off") {
|
|
33072
|
-
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`.");
|
|
33073
33004
|
}
|
|
33074
33005
|
if (jev.stillOn) {
|
|
33075
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\`.`);
|
|
@@ -33131,11 +33062,12 @@ var init_cloud_connection = __esm(() => {
|
|
|
33131
33062
|
init_cloud_enrollment();
|
|
33132
33063
|
init_fp_home();
|
|
33133
33064
|
init_fp_config();
|
|
33065
|
+
init_effective_reviewers();
|
|
33134
33066
|
init_collector_config();
|
|
33135
33067
|
});
|
|
33136
33068
|
|
|
33137
33069
|
// src/hooks/setup-state.ts
|
|
33138
|
-
import { existsSync as existsSync34, realpathSync as
|
|
33070
|
+
import { existsSync as existsSync34, realpathSync as realpathSync3, statSync as statSync24 } from "node:fs";
|
|
33139
33071
|
import { dirname as dirname19, resolve as resolve27 } from "node:path";
|
|
33140
33072
|
import { homedir as homedir26 } from "node:os";
|
|
33141
33073
|
function globalConfigPath(home = homedir26()) {
|
|
@@ -33179,7 +33111,7 @@ function hasGlobalHooksInstalled() {
|
|
|
33179
33111
|
function realResolve(path) {
|
|
33180
33112
|
const lexical = resolve27(path);
|
|
33181
33113
|
try {
|
|
33182
|
-
return
|
|
33114
|
+
return realpathSync3(lexical);
|
|
33183
33115
|
} catch {
|
|
33184
33116
|
return lexical;
|
|
33185
33117
|
}
|
|
@@ -33350,7 +33282,7 @@ var init_onboarding_lock = __esm(() => {
|
|
|
33350
33282
|
|
|
33351
33283
|
// lib/claude-sessions.ts
|
|
33352
33284
|
import { readdirSync as readdirSync19, statSync as statSync25, existsSync as existsSync36 } from "node:fs";
|
|
33353
|
-
import { join as join34, basename as
|
|
33285
|
+
import { join as join34, basename as basename8 } from "node:path";
|
|
33354
33286
|
function getClaudeProjectsRoot() {
|
|
33355
33287
|
return getClaudeProjectsPath();
|
|
33356
33288
|
}
|
|
@@ -37488,7 +37420,7 @@ function surveyReviewableCoverage(cwd) {
|
|
|
37488
37420
|
records.push(...selected ? pack.policies.filter((p) => selected.includes(p.name)) : pack.policies);
|
|
37489
37421
|
}
|
|
37490
37422
|
const asked = semanticPoliciesFromPacks(jevPacks(packs));
|
|
37491
|
-
reviewers =
|
|
37423
|
+
reviewers = new Set(asked.policies.map((p) => p.name));
|
|
37492
37424
|
} catch {}
|
|
37493
37425
|
let config;
|
|
37494
37426
|
try {
|
|
@@ -37520,7 +37452,7 @@ function surveyReviewableCoverage(cwd) {
|
|
|
37520
37452
|
} catch {
|
|
37521
37453
|
customFiles = 0;
|
|
37522
37454
|
}
|
|
37523
|
-
return { ...countReviewable(records, reviewers), customFiles };
|
|
37455
|
+
return { ...countReviewable(records, reviewers), customFiles, jevChecks: reviewers?.size ?? 0 };
|
|
37524
37456
|
}
|
|
37525
37457
|
function reviewableSummary(coverage) {
|
|
37526
37458
|
const { enabled, reviewable } = coverage;
|
|
@@ -37533,11 +37465,13 @@ function reviewableSummary(coverage) {
|
|
|
37533
37465
|
return `${reviewable} of ${noun} reviewable: Jev may clear a deny or an instruction from those, and from no others${tail}.`;
|
|
37534
37466
|
}
|
|
37535
37467
|
function reviewableProblem(coverage) {
|
|
37468
|
+
if (coverage.jevChecks === 0)
|
|
37469
|
+
return NO_JEV_CHECKS_PROBLEM;
|
|
37536
37470
|
if (coverage.enabled === 0 || coverage.reviewable > 0 || coverage.customFiles > 0)
|
|
37537
37471
|
return null;
|
|
37538
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.";
|
|
37539
37473
|
}
|
|
37540
|
-
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;
|
|
37541
37475
|
var init_policy_reviewability = __esm(() => {
|
|
37542
37476
|
init_cloud_managed_policies();
|
|
37543
37477
|
init_effective_reviewers();
|
|
@@ -37549,6 +37483,7 @@ var init_policy_reviewability = __esm(() => {
|
|
|
37549
37483
|
init_policy_catalog();
|
|
37550
37484
|
init_policy_registry();
|
|
37551
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}\`.`;
|
|
37552
37487
|
});
|
|
37553
37488
|
|
|
37554
37489
|
// src/hooks/semantic/jev-stats.ts
|
|
@@ -37594,14 +37529,14 @@ function computeJevStats(entries, opts = {}) {
|
|
|
37594
37529
|
notConsulted: 0,
|
|
37595
37530
|
noRequest: 0,
|
|
37596
37531
|
decisions: { allow: 0, instruct: 0, deny: 0 },
|
|
37597
|
-
modes: {
|
|
37598
|
-
|
|
37532
|
+
modes: { observe: 0, enforce: 0 },
|
|
37533
|
+
observeClearsByPolicy: {},
|
|
37599
37534
|
models: {}
|
|
37600
37535
|
};
|
|
37601
37536
|
const latencies = [];
|
|
37602
37537
|
const fallbackReasons = new Counter;
|
|
37603
37538
|
const clearsByPolicy = new Counter;
|
|
37604
|
-
const
|
|
37539
|
+
const observeClearsByPolicy = new Counter;
|
|
37605
37540
|
const models = new Counter;
|
|
37606
37541
|
for (const raw of entries) {
|
|
37607
37542
|
if (typeof raw.timestamp !== "number" || raw.timestamp < since || raw.timestamp > now)
|
|
@@ -37633,13 +37568,13 @@ function computeJevStats(entries, opts = {}) {
|
|
|
37633
37568
|
latencies.push(e.jevLatencyMs);
|
|
37634
37569
|
if (e.jevModel)
|
|
37635
37570
|
models.bump(e.jevModel);
|
|
37636
|
-
const clears = e.jevMode === "
|
|
37571
|
+
const clears = e.jevMode === "observe" ? observeClearsByPolicy : clearsByPolicy;
|
|
37637
37572
|
for (const name of e.jevCleared ?? [])
|
|
37638
37573
|
clears.bump(name);
|
|
37639
37574
|
}
|
|
37640
37575
|
stats.fallbackReasons = fallbackReasons.toRecord();
|
|
37641
37576
|
stats.clearsByPolicy = clearsByPolicy.toRecord();
|
|
37642
|
-
stats.
|
|
37577
|
+
stats.observeClearsByPolicy = observeClearsByPolicy.toRecord();
|
|
37643
37578
|
stats.models = models.toRecord();
|
|
37644
37579
|
stats.fallbackRate = stats.total > 0 ? stats.fallbacks / stats.total : 0;
|
|
37645
37580
|
latencies.sort((a, b) => a - b);
|
|
@@ -37705,7 +37640,7 @@ function parseFlags(argv, allowed) {
|
|
|
37705
37640
|
function modeLine(mode) {
|
|
37706
37641
|
if (mode === "off")
|
|
37707
37642
|
return "off — Jev is not asked at all; the regex policies decide alone";
|
|
37708
|
-
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";
|
|
37709
37644
|
}
|
|
37710
37645
|
function providerLabel(provider) {
|
|
37711
37646
|
return provider === JEV_CLOUD_PROVIDER ? "FailproofAI Cloud" : provider;
|
|
@@ -37836,14 +37771,14 @@ function jevStatsLines(stats, opts = {}) {
|
|
|
37836
37771
|
const reasons = Object.entries(stats.fallbackReasons).sort((a, b) => b[1] - a[1]).map(([r, n]) => `${r} ×${n}`).join(", ");
|
|
37837
37772
|
const byPolicy = (counts) => Object.entries(counts ?? {}).sort((a, b) => b[1] - a[1]).map(([p, n]) => `${p} ×${n}`).join(", ");
|
|
37838
37773
|
const clears = byPolicy(stats.clearsByPolicy);
|
|
37839
|
-
const
|
|
37774
|
+
const observeClears = byPolicy(stats.observeClearsByPolicy);
|
|
37840
37775
|
const ms = (v) => v === null ? "—" : `${Math.round(v)} ms`;
|
|
37841
37776
|
return stack(heading, rows([
|
|
37842
37777
|
["evaluations", String(stats.total)],
|
|
37843
37778
|
["fell back to regex", `${pct(stats.fallbackRate)}${reasons ? ` (${reasons})` : ""}`],
|
|
37844
37779
|
["latency", `p50 ${ms(stats.latencyP50Ms)} · p95 ${ms(stats.latencyP95Ms)}`],
|
|
37845
37780
|
["cleared", clears || "nothing"],
|
|
37846
|
-
...
|
|
37781
|
+
...observeClears ? [["would have cleared (observe)", observeClears]] : []
|
|
37847
37782
|
], opts));
|
|
37848
37783
|
}
|
|
37849
37784
|
function originOf2(provider, baseUrl) {
|
|
@@ -38295,8 +38230,7 @@ async function cloudSetup(values, bools, opts) {
|
|
|
38295
38230
|
delete next.accountId;
|
|
38296
38231
|
if (!sameProvider && existing?.raw.timeoutMs !== undefined)
|
|
38297
38232
|
next.timeoutMs = existing.raw.timeoutMs;
|
|
38298
|
-
|
|
38299
|
-
next.mode = mode;
|
|
38233
|
+
next.mode = values.get("--mode") ?? (sameProvider && typeof next.mode === "string" ? next.mode : "observe");
|
|
38300
38234
|
if (values.has("--timeout-ms")) {
|
|
38301
38235
|
const rawTimeout = values.get("--timeout-ms");
|
|
38302
38236
|
const n = Number(rawTimeout);
|
|
@@ -38325,7 +38259,7 @@ async function cloudSetup(values, bools, opts) {
|
|
|
38325
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):`
|
|
38326
38260
|
] : [
|
|
38327
38261
|
"Not saved: this machine is not connected to FailproofAI Cloud with a key that carries jev:evaluate.",
|
|
38328
|
-
'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:'
|
|
38329
38263
|
],
|
|
38330
38264
|
" failproofai config --token <key>",
|
|
38331
38265
|
"",
|
|
@@ -38392,7 +38326,7 @@ function displayOnlyCloudOrigin(routing) {
|
|
|
38392
38326
|
function namedEndpoint(raw) {
|
|
38393
38327
|
if (!raw || typeof raw.provider !== "string" || !JEV_PROVIDER_KINDS.includes(raw.provider))
|
|
38394
38328
|
return null;
|
|
38395
|
-
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) };
|
|
38396
38330
|
if (raw.baseUrl !== undefined)
|
|
38397
38331
|
routing.baseUrl = raw.baseUrl;
|
|
38398
38332
|
if (raw.accountId !== undefined)
|
|
@@ -38501,6 +38435,7 @@ async function status(argv, opts) {
|
|
|
38501
38435
|
enabled: coverage.enabled,
|
|
38502
38436
|
reviewable: coverage.reviewable,
|
|
38503
38437
|
customPolicyFiles: coverage.customFiles,
|
|
38438
|
+
jevChecks: coverage.jevChecks,
|
|
38504
38439
|
problem: coverageProblem
|
|
38505
38440
|
}
|
|
38506
38441
|
} : {}
|
|
@@ -38543,7 +38478,7 @@ async function status(argv, opts) {
|
|
|
38543
38478
|
["mode", modeLine("off")],
|
|
38544
38479
|
["config", inspection.path],
|
|
38545
38480
|
...r.provider === JEV_CLOUD_PROVIDER ? [["key", cloudKeyRow()]] : []
|
|
38546
|
-
], 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)));
|
|
38547
38482
|
}
|
|
38548
38483
|
if (inspection.status === "not-connected") {
|
|
38549
38484
|
const r = inspection.routing;
|
|
@@ -38615,7 +38550,7 @@ async function test(argv, deps, opts) {
|
|
|
38615
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}.`;
|
|
38616
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";
|
|
38617
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;
|
|
38618
|
-
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";
|
|
38619
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;
|
|
38620
38555
|
const json = asJson ? JSON.stringify({ ok: false, error: { code, message: why } }, null, 2) : undefined;
|
|
38621
38556
|
return fail2(stack(title("failproofai jev test", "not run", opts), note(why, opts), nextStep(fixCmd, fixLead, opts)), json);
|
|
@@ -38844,7 +38779,7 @@ var init_jev_cli = __esm(() => {
|
|
|
38844
38779
|
"Usage:",
|
|
38845
38780
|
" failproofai jev --url <url> [--key-stdin | --token <token>] [options]",
|
|
38846
38781
|
" failproofai jev setup --provider <kind> [--key-stdin | --key-from-env] [options]",
|
|
38847
|
-
" failproofai jev setup --provider failproofai [--mode off|
|
|
38782
|
+
" failproofai jev setup --provider failproofai [--mode off|observe|enforce] (FailproofAI Cloud: no key, no URL)",
|
|
38848
38783
|
" failproofai jev status [--json]",
|
|
38849
38784
|
" failproofai jev test [--json]",
|
|
38850
38785
|
" failproofai jev models [--provider <kind>] [--url <base>] [--json]",
|
|
@@ -38954,7 +38889,7 @@ import {
|
|
|
38954
38889
|
rmSync as rmSync16,
|
|
38955
38890
|
writeFileSync as writeFileSync26
|
|
38956
38891
|
} from "node:fs";
|
|
38957
|
-
import { basename as
|
|
38892
|
+
import { basename as basename9, dirname as dirname25, join as join39, resolve as resolve32 } from "node:path";
|
|
38958
38893
|
function migrateToLayout42() {
|
|
38959
38894
|
const moves = [
|
|
38960
38895
|
{ from: legacy.authJson(), to: auditSessionFile() },
|
|
@@ -38990,7 +38925,7 @@ function migrateToLayout42() {
|
|
|
38990
38925
|
}
|
|
38991
38926
|
rmSync16(from, { force: true });
|
|
38992
38927
|
}
|
|
38993
|
-
migrated.push(`${
|
|
38928
|
+
migrated.push(`${basename9(from)} → audit/${basename9(to)}`);
|
|
38994
38929
|
}
|
|
38995
38930
|
for (const secret of [auditSessionFile()]) {
|
|
38996
38931
|
if (!existsSync46(secret))
|
|
@@ -39034,7 +38969,7 @@ function appendLedger2(entry) {
|
|
|
39034
38969
|
} catch {}
|
|
39035
38970
|
}
|
|
39036
38971
|
function backupNameOf2(f) {
|
|
39037
|
-
return f.as ??
|
|
38972
|
+
return f.as ?? basename9(f.at());
|
|
39038
38973
|
}
|
|
39039
38974
|
function pruneMigratedCredentials2(from) {
|
|
39040
38975
|
const pruned = [];
|
|
@@ -39205,7 +39140,7 @@ var init_migrations2 = __esm(() => {
|
|
|
39205
39140
|
{ at: legacy.auditSchedule }
|
|
39206
39141
|
];
|
|
39207
39142
|
PRUNED_AFTER_SUCCESS2 = [
|
|
39208
|
-
{ as:
|
|
39143
|
+
{ as: basename9(legacy.authJson()), landsAt: auditSessionFile }
|
|
39209
39144
|
];
|
|
39210
39145
|
});
|
|
39211
39146
|
|
|
@@ -39556,6 +39491,34 @@ async function installDaemonService2() {
|
|
|
39556
39491
|
}
|
|
39557
39492
|
return { installed: true };
|
|
39558
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
|
+
}
|
|
39559
39522
|
async function daemonAcceptsConnectionsQuietly2() {
|
|
39560
39523
|
try {
|
|
39561
39524
|
await Promise.resolve().then(() => init_daemon_client2());
|
|
@@ -39564,6 +39527,27 @@ async function daemonAcceptsConnectionsQuietly2() {
|
|
|
39564
39527
|
return false;
|
|
39565
39528
|
}
|
|
39566
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
|
+
}
|
|
39567
39551
|
async function waitForDaemonRunning2(deps = {}) {
|
|
39568
39552
|
const status = deps.status ?? daemonServiceStatus2;
|
|
39569
39553
|
const accepts = deps.accepts ?? daemonAcceptsConnectionsQuietly2;
|
|
@@ -39668,7 +39652,7 @@ function daemonServiceStatus2() {
|
|
|
39668
39652
|
return "stopped";
|
|
39669
39653
|
}
|
|
39670
39654
|
}
|
|
39671
|
-
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;
|
|
39672
39656
|
var init_daemon_service2 = __esm(() => {
|
|
39673
39657
|
init_hook_logger();
|
|
39674
39658
|
init_daemon_download();
|
|
@@ -39678,6 +39662,44 @@ var init_daemon_service2 = __esm(() => {
|
|
|
39678
39662
|
UNIT_UNSAFE_RE2 = /["\\\r\n]/;
|
|
39679
39663
|
});
|
|
39680
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
|
+
|
|
39681
39703
|
// src/hooks/uninstall-cli.ts
|
|
39682
39704
|
import { existsSync as existsSync48, rmSync as rmSync18 } from "node:fs";
|
|
39683
39705
|
function survey(cwd) {
|
|
@@ -39857,8 +39879,8 @@ var init_uninstall_cli = __esm(() => {
|
|
|
39857
39879
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
39858
39880
|
import { homedir as homedir32, tmpdir as tmpdir4 } from "node:os";
|
|
39859
39881
|
import { createHash as createHash11 } from "node:crypto";
|
|
39860
|
-
import { copyFileSync as copyFileSync4, existsSync as existsSync49, mkdirSync as mkdirSync31, mkdtempSync as mkdtempSync3, readFileSync as readFileSync38, readdirSync as readdirSync23, realpathSync as
|
|
39861
|
-
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";
|
|
39862
39884
|
function parseList(rest, ...flags) {
|
|
39863
39885
|
let found = false;
|
|
39864
39886
|
const out = [];
|
|
@@ -40058,10 +40080,10 @@ async function build(rest) {
|
|
|
40058
40080
|
const requiredCli = semantic.length > 0 ? minCliVersion ?? JEV_PACK_MIN_CLI : minCliVersion;
|
|
40059
40081
|
const questionCost = semantic.reduce((total, entry) => total + questionChars(entry), 0);
|
|
40060
40082
|
const firstParty = isFirstPartyPack(packSource);
|
|
40061
|
-
const questionBudget = MAX_PACK_QUESTION_CHARS - (firstParty ? 0 :
|
|
40083
|
+
const questionBudget = MAX_PACK_QUESTION_CHARS - (firstParty ? 0 : JEV_POLICIES_QUESTION_CHARS);
|
|
40062
40084
|
if (questionCost > questionBudget) {
|
|
40063
40085
|
return fail3([
|
|
40064
|
-
`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.`),
|
|
40065
40087
|
"Shorten the probe instructions and criteria, or ship fewer policies per pack."
|
|
40066
40088
|
]);
|
|
40067
40089
|
}
|
|
@@ -40129,7 +40151,7 @@ ${sha(readFileSync38(entryOut))} ${PACK_ENTRY_ASSET}
|
|
|
40129
40151
|
return ok3([
|
|
40130
40152
|
`Built ${identity.id}@${identity.version} — ${policies.length} policies, ${on} on by default.`,
|
|
40131
40153
|
...semantic.length > 0 ? [
|
|
40132
|
-
` ${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.")
|
|
40133
40155
|
] : [],
|
|
40134
40156
|
...requiredCli ? [` Requires failproofai ${requiredCli} or newer.`] : [],
|
|
40135
40157
|
...semanticOnly ? JEV_ONLY_ROLLBACK_REMINDER : [],
|
|
@@ -40365,7 +40387,7 @@ function porcelainEntries(status) {
|
|
|
40365
40387
|
}
|
|
40366
40388
|
function realOrSelf(path) {
|
|
40367
40389
|
try {
|
|
40368
|
-
return
|
|
40390
|
+
return realpathSync4(path);
|
|
40369
40391
|
} catch {
|
|
40370
40392
|
return path;
|
|
40371
40393
|
}
|
|
@@ -40920,12 +40942,12 @@ async function publish(rest) {
|
|
|
40920
40942
|
if (askingWhere) {
|
|
40921
40943
|
const found = discovered.length > 0 ? discovered : [entry];
|
|
40922
40944
|
process.stdout.write(`
|
|
40923
|
-
${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(", ")}
|
|
40924
40946
|
|
|
40925
40947
|
`);
|
|
40926
40948
|
const token = githubToken();
|
|
40927
40949
|
const login = token ? await credentialLogin(token) : null;
|
|
40928
|
-
const folder =
|
|
40950
|
+
const folder = basename10(resolve34(entry, ".."));
|
|
40929
40951
|
const answered = await askWhereToPublish(`${login ?? "your-account"}/${folder}`, { stdin: process.stdin, stdout: process.stdout });
|
|
40930
40952
|
if (answered === null)
|
|
40931
40953
|
return ok3(["Nothing was published."]);
|
|
@@ -40936,7 +40958,7 @@ async function publish(rest) {
|
|
|
40936
40958
|
let versionFromSha = false;
|
|
40937
40959
|
const dryRun = rest.includes("--dry-run") || !repo;
|
|
40938
40960
|
const allowPrivate = rest.includes("--allow-private");
|
|
40939
|
-
const id = flag("id") ?? repo ?? (dryRun && entry ? `local/${
|
|
40961
|
+
const id = flag("id") ?? repo ?? (dryRun && entry ? `local/${basename10(resolve34(entry, ".."))}` : undefined);
|
|
40940
40962
|
const [owner, name] = (repo ?? "/").split("/");
|
|
40941
40963
|
if (repo && (!owner || !name || repo.split("/").length !== 2)) {
|
|
40942
40964
|
return fail3([`--repo must be <owner>/<repo>, got ${JSON.stringify(repo)}`]);
|
|
@@ -41176,11 +41198,11 @@ async function pickClis(io) {
|
|
|
41176
41198
|
function semanticPhrase(count) {
|
|
41177
41199
|
return `${count} Jev ${count === 1 ? "check" : "checks"}`;
|
|
41178
41200
|
}
|
|
41179
|
-
function
|
|
41201
|
+
function howJevAsksChecks(pack) {
|
|
41180
41202
|
if (jevPacks([{ effect: pack.effect ?? "enforce", clis: null }]).length === 0) {
|
|
41181
41203
|
return "not asked: this pack only observes, and Jev asks only the checks of packs that enforce";
|
|
41182
41204
|
}
|
|
41183
|
-
const how =
|
|
41205
|
+
const how = "asked by Jev on every tool call they apply to";
|
|
41184
41206
|
return pack.clis && pack.clis.length > 0 ? `${how}, for ${pack.clis.join(", ")} only` : how;
|
|
41185
41207
|
}
|
|
41186
41208
|
function jevCheckWarnings(id, preview) {
|
|
@@ -41217,7 +41239,7 @@ async function pickFromSource(source, io) {
|
|
|
41217
41239
|
const on = preview.policies.filter((p) => p.defaultEnabled).length;
|
|
41218
41240
|
if (preview.semantic.length > 0) {
|
|
41219
41241
|
io.stdout.write(`
|
|
41220
|
-
This pack also brings ${semanticPhrase(preview.semantic.length)} (not selectable), ` + `${
|
|
41242
|
+
This pack also brings ${semanticPhrase(preview.semantic.length)} (not selectable), ` + `${howJevAsksChecks(preview)}.
|
|
41221
41243
|
See: failproofai policies show ${source}
|
|
41222
41244
|
|
|
41223
41245
|
`);
|
|
@@ -41284,7 +41306,7 @@ async function add(rest) {
|
|
|
41284
41306
|
}[result.selection];
|
|
41285
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)}`);
|
|
41286
41308
|
if (result.semantic > 0) {
|
|
41287
|
-
lines.push(` ${semanticPhrase(result.semantic)}, ${
|
|
41309
|
+
lines.push(` ${semanticPhrase(result.semantic)}, ${howJevAsksChecks(result)}. ` + "They apply only where you configured Jev (`failproofai jev status`).");
|
|
41288
41310
|
}
|
|
41289
41311
|
if (result.minCliVersionNote)
|
|
41290
41312
|
lines.push(` ${result.minCliVersionNote}`);
|
|
@@ -41475,7 +41497,7 @@ function jevChecksSection(pack, opts) {
|
|
|
41475
41497
|
...rule(`Jev checks — ${pack.semantic.length} · not selectable · only where Jev is configured`, opts),
|
|
41476
41498
|
...table({ head: ["", "", ""], rows, protect: [0, 1] }, opts),
|
|
41477
41499
|
"",
|
|
41478
|
-
...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),
|
|
41479
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)
|
|
41480
41502
|
];
|
|
41481
41503
|
}
|
|
@@ -41884,7 +41906,7 @@ var init_install_prompt = __esm(() => {
|
|
|
41884
41906
|
// src/hooks/manager.ts
|
|
41885
41907
|
import { execSync as execSync7 } from "node:child_process";
|
|
41886
41908
|
import { existsSync as existsSync50 } from "node:fs";
|
|
41887
|
-
import { resolve as resolve35, basename as
|
|
41909
|
+
import { resolve as resolve35, basename as basename11 } from "node:path";
|
|
41888
41910
|
import { platform as platform4, arch as arch4, release as release3, hostname as hostname6 } from "node:os";
|
|
41889
41911
|
function getSettingsPath2(scope, cwd) {
|
|
41890
41912
|
return claudeCode.getSettingsPath(scope, cwd);
|
|
@@ -42159,7 +42181,7 @@ Validated ${validatedHooks.length} custom hook(s) from ${path}: ${validatedHooks
|
|
|
42159
42181
|
const alwaysOnNames = new Set(BUILTIN_POLICIES.filter((p) => p.alwaysOn).map((p) => p.name));
|
|
42160
42182
|
const fromPack = policyNames === undefined ? [] : selectedPolicies.filter((name) => !alwaysOnNames.has(name));
|
|
42161
42183
|
if (fromPack.length > 0) {
|
|
42162
|
-
if (!
|
|
42184
|
+
if (!hasInstalledRegexPacks()) {
|
|
42163
42185
|
try {
|
|
42164
42186
|
await addPack(CORE_SOURCE, { only: fromPack });
|
|
42165
42187
|
} catch (err) {
|
|
@@ -42545,7 +42567,7 @@ async function listHooks(cwd) {
|
|
|
42545
42567
|
groups.push(note(dir, opts));
|
|
42546
42568
|
const rows = [];
|
|
42547
42569
|
for (const file of files) {
|
|
42548
|
-
const filename =
|
|
42570
|
+
const filename = basename11(file);
|
|
42549
42571
|
try {
|
|
42550
42572
|
const hooks = await loadCustomHooks(file);
|
|
42551
42573
|
record(filename, hooks.map((h) => h.name));
|
|
@@ -43069,13 +43091,13 @@ function parseScriptArgs(argv) {
|
|
|
43069
43091
|
var init_parse_script_args = () => {};
|
|
43070
43092
|
|
|
43071
43093
|
// scripts/install-diagnosis.mjs
|
|
43072
|
-
import { existsSync as existsSync51, readFileSync as readFileSync40, realpathSync as
|
|
43094
|
+
import { existsSync as existsSync51, readFileSync as readFileSync40, realpathSync as realpathSync5 } from "node:fs";
|
|
43073
43095
|
import { dirname as dirname27, resolve as resolve37 } from "node:path";
|
|
43074
43096
|
import { homedir as homedir33, platform as platform5 } from "node:os";
|
|
43075
43097
|
import { spawnSync } from "node:child_process";
|
|
43076
43098
|
function findPackageRoot(start) {
|
|
43077
43099
|
try {
|
|
43078
|
-
let dir =
|
|
43100
|
+
let dir = realpathSync5(start);
|
|
43079
43101
|
if (existsSync51(dir) && !existsSync51(resolve37(dir, "package.json"))) {
|
|
43080
43102
|
dir = dirname27(dir);
|
|
43081
43103
|
}
|
|
@@ -43155,7 +43177,7 @@ function buildRecommendation(pathFirstBin) {
|
|
|
43155
43177
|
function diagnoseShadow(self) {
|
|
43156
43178
|
const selfPackageRoot = (() => {
|
|
43157
43179
|
try {
|
|
43158
|
-
return self?.selfPackageRoot ?
|
|
43180
|
+
return self?.selfPackageRoot ? realpathSync5(self.selfPackageRoot) : null;
|
|
43159
43181
|
} catch {
|
|
43160
43182
|
return self?.selfPackageRoot ?? null;
|
|
43161
43183
|
}
|
|
@@ -43239,7 +43261,7 @@ var init_dashboard_host = __esm(() => {
|
|
|
43239
43261
|
|
|
43240
43262
|
// scripts/launch.ts
|
|
43241
43263
|
import { spawn as spawn2 } from "child_process";
|
|
43242
|
-
import { realpathSync as
|
|
43264
|
+
import { realpathSync as realpathSync6, existsSync as existsSync52 } from "node:fs";
|
|
43243
43265
|
import { resolve as resolve38, dirname as dirname28 } from "node:path";
|
|
43244
43266
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
43245
43267
|
import { createInterface } from "node:readline";
|
|
@@ -43264,7 +43286,7 @@ function launch(mode) {
|
|
|
43264
43286
|
process.env.PORT = port;
|
|
43265
43287
|
process.env.HOSTNAME = bindHost;
|
|
43266
43288
|
cmd = "node";
|
|
43267
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve38(dirname28(
|
|
43289
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve38(dirname28(realpathSync6(fileURLToPath2(import.meta.url))), "..");
|
|
43268
43290
|
const serverJsPath = resolve38(packageRoot, ".next/standalone/server.js");
|
|
43269
43291
|
if (!existsSync52(serverJsPath)) {
|
|
43270
43292
|
let shadowMessage = null;
|
|
@@ -44044,11 +44066,15 @@ import {
|
|
|
44044
44066
|
existsSync as existsSync53,
|
|
44045
44067
|
lstatSync as lstatSync3,
|
|
44046
44068
|
mkdirSync as mkdirSync32,
|
|
44069
|
+
readlinkSync as readlinkSync2,
|
|
44070
|
+
realpathSync as realpathSync7,
|
|
44047
44071
|
renameSync as renameSync13,
|
|
44048
44072
|
rmSync as rmSync20,
|
|
44073
|
+
symlinkSync as symlinkSync2,
|
|
44049
44074
|
unlinkSync as unlinkSync10
|
|
44050
44075
|
} from "node:fs";
|
|
44051
|
-
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";
|
|
44052
44078
|
function readYamlDoc2(path) {
|
|
44053
44079
|
try {
|
|
44054
44080
|
return existsSync53(path) ? $parseDocument(readFileSync41(path, "utf8")) : $parseDocument("");
|
|
@@ -44065,21 +44091,71 @@ function isMarkedHook2(hook) {
|
|
|
44065
44091
|
const cmd = typeof h.command === "string" ? h.command : "";
|
|
44066
44092
|
return cmd.includes("failproofai") && cmd.includes("--hook");
|
|
44067
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
|
+
}
|
|
44068
44100
|
function hermesPluginPathForSettings2(settingsPath) {
|
|
44069
44101
|
return resolve39(dirname29(settingsPath), "plugins", HERMES_PLUGIN_ID2);
|
|
44070
44102
|
}
|
|
44071
|
-
function
|
|
44072
|
-
|
|
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;
|
|
44073
44111
|
try {
|
|
44074
|
-
|
|
44075
|
-
|
|
44076
|
-
|
|
44077
|
-
|
|
44078
|
-
|
|
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);
|
|
44079
44123
|
} catch {
|
|
44080
44124
|
return false;
|
|
44081
44125
|
}
|
|
44082
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
|
+
}
|
|
44083
44159
|
function hermesConfigState2(settingsPath) {
|
|
44084
44160
|
if (!existsSync53(settingsPath)) {
|
|
44085
44161
|
return { pluginEnabled: false, legacyShellHookPresent: false };
|
|
@@ -44097,20 +44173,31 @@ function hermesProfileHealth() {
|
|
|
44097
44173
|
return listHermesProfiles().map((profile) => {
|
|
44098
44174
|
const settingsPath = resolve39(profile.home, "config.yaml");
|
|
44099
44175
|
const config = hermesConfigState2(settingsPath);
|
|
44100
|
-
const
|
|
44176
|
+
const plugin = hermesPluginState2(settingsPath);
|
|
44177
|
+
const pluginInstalled = (plugin.kind === "link" || plugin.kind === "copy") && plugin.complete;
|
|
44178
|
+
const pluginWorks = pluginInstalled && config.pluginEnabled;
|
|
44101
44179
|
return {
|
|
44102
44180
|
name: profile.name,
|
|
44103
44181
|
home: profile.home,
|
|
44104
44182
|
settingsPath,
|
|
44105
44183
|
pluginInstalled,
|
|
44184
|
+
pluginMode: plugin.kind === "link" || plugin.kind === "copy" ? plugin.kind : null,
|
|
44106
44185
|
pluginEnabled: config.pluginEnabled,
|
|
44107
44186
|
legacyShellHookPresent: config.legacyShellHookPresent,
|
|
44108
|
-
|
|
44187
|
+
cronUnchecked: config.legacyShellHookPresent && !pluginWorks,
|
|
44188
|
+
healthy: pluginWorks && !config.legacyShellHookPresent
|
|
44109
44189
|
};
|
|
44110
44190
|
});
|
|
44111
44191
|
}
|
|
44112
44192
|
function hermesProfileStatusRows() {
|
|
44113
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
|
+
}
|
|
44114
44201
|
const problems = [];
|
|
44115
44202
|
if (!profile.pluginInstalled)
|
|
44116
44203
|
problems.push("plugin files missing or incomplete");
|
|
@@ -44118,13 +44205,15 @@ function hermesProfileStatusRows() {
|
|
|
44118
44205
|
problems.push("plugin not enabled");
|
|
44119
44206
|
if (profile.legacyShellHookPresent)
|
|
44120
44207
|
problems.push("legacy shell hook also present");
|
|
44208
|
+
if (problems.length > 0)
|
|
44209
|
+
return [label, "UNHEALTHY — " + problems.join("; ")];
|
|
44121
44210
|
return [
|
|
44122
|
-
|
|
44123
|
-
|
|
44211
|
+
label,
|
|
44212
|
+
profile.pluginMode === "copy" ? "native plugin enabled (copied — `failproofai update` links it so upgrades apply)" : "native plugin enabled"
|
|
44124
44213
|
];
|
|
44125
44214
|
});
|
|
44126
44215
|
}
|
|
44127
|
-
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;
|
|
44128
44217
|
var init_integrations2 = __esm(() => {
|
|
44129
44218
|
init_dist();
|
|
44130
44219
|
init_hermes_profiles();
|
|
@@ -44136,9 +44225,9 @@ var init_integrations2 = __esm(() => {
|
|
|
44136
44225
|
|
|
44137
44226
|
// scripts/launch.ts
|
|
44138
44227
|
import { spawn as spawn3 } from "child_process";
|
|
44139
|
-
import { realpathSync as
|
|
44228
|
+
import { realpathSync as realpathSync8, existsSync as existsSync54 } from "node:fs";
|
|
44140
44229
|
import { resolve as resolve40, dirname as dirname30 } from "node:path";
|
|
44141
|
-
import { fileURLToPath as
|
|
44230
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
44142
44231
|
import { createInterface as createInterface2 } from "node:readline";
|
|
44143
44232
|
function launch2(mode) {
|
|
44144
44233
|
const { loggingLevel, disableTelemetry, allowedDevOrigins, host, remainingArgs } = parseScriptArgs(process.argv.slice(2));
|
|
@@ -44161,7 +44250,7 @@ function launch2(mode) {
|
|
|
44161
44250
|
process.env.PORT = port;
|
|
44162
44251
|
process.env.HOSTNAME = bindHost;
|
|
44163
44252
|
cmd = "node";
|
|
44164
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve40(dirname30(
|
|
44253
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve40(dirname30(realpathSync8(fileURLToPath4(import.meta.url))), "..");
|
|
44165
44254
|
const serverJsPath = resolve40(packageRoot, ".next/standalone/server.js");
|
|
44166
44255
|
if (!existsSync54(serverJsPath)) {
|
|
44167
44256
|
let shadowMessage = null;
|
|
@@ -44259,18 +44348,18 @@ var init_cli_error2 = __esm(() => {
|
|
|
44259
44348
|
});
|
|
44260
44349
|
|
|
44261
44350
|
// bin/failproofai.mjs
|
|
44262
|
-
import { realpathSync as
|
|
44351
|
+
import { realpathSync as realpathSync9 } from "fs";
|
|
44263
44352
|
import { dirname as dirname31, resolve as resolve41 } from "path";
|
|
44264
|
-
import { fileURLToPath as
|
|
44353
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
44265
44354
|
// package.json
|
|
44266
|
-
var version = "1.0.
|
|
44355
|
+
var version = "1.0.9-beta.0";
|
|
44267
44356
|
|
|
44268
44357
|
// bin/failproofai.mjs
|
|
44269
44358
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
44270
|
-
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve41(dirname31(
|
|
44359
|
+
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve41(dirname31(realpathSync9(fileURLToPath5(import.meta.url))), "..");
|
|
44271
44360
|
}
|
|
44272
44361
|
if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
44273
|
-
process.env.FAILPROOFAI_DIST_PATH = resolve41(dirname31(
|
|
44362
|
+
process.env.FAILPROOFAI_DIST_PATH = resolve41(dirname31(realpathSync9(fileURLToPath5(import.meta.url))), "..", "dist");
|
|
44274
44363
|
}
|
|
44275
44364
|
var args = process.argv.slice(2);
|
|
44276
44365
|
if (args[0] === "p" || args[0] === "policy")
|
|
@@ -44844,7 +44933,7 @@ Run \`failproofai backfill --help\` for usage.`);
|
|
|
44844
44933
|
"",
|
|
44845
44934
|
"FailproofAI Cloud needs no setup here: `failproofai config --token",
|
|
44846
44935
|
'<key>` with a key that carries jev:evaluate (the "machine" preset)',
|
|
44847
|
-
"turns it on in
|
|
44936
|
+
"turns it on in observe mode when there is no jev.json yet \u2014 except",
|
|
44848
44937
|
"with --no-transcripts, which only stores the key; then",
|
|
44849
44938
|
"`failproofai jev setup --provider failproofai` switches it on. Its",
|
|
44850
44939
|
"key stays in credentials.json; no --url ever selects it."
|
|
@@ -44861,7 +44950,7 @@ Run \`failproofai backfill --help\` for usage.`);
|
|
|
44861
44950
|
["--account-id <id>", "Cloudflare account id (32 hex characters)."],
|
|
44862
44951
|
["--base-url <url>", "Override the API base; `default` clears it."],
|
|
44863
44952
|
["--model <id>", "Override the model id; `default` clears it."],
|
|
44864
|
-
["--mode <m>", "enforce (default),
|
|
44953
|
+
["--mode <m>", "enforce (default), observe (log Jev, enforce regex), or off."],
|
|
44865
44954
|
["--timeout-ms <n>", "Per-call budget before falling back. Default 3000."]
|
|
44866
44955
|
]
|
|
44867
44956
|
},
|
|
@@ -44872,7 +44961,7 @@ Run \`failproofai backfill --help\` for usage.`);
|
|
|
44872
44961
|
" else can read \u2014 or one in a directory anyone else can WRITE, who",
|
|
44873
44962
|
" could replace it \u2014 is refused; a repository can never set it.",
|
|
44874
44963
|
"\u2022 Re-running setup for the same provider keeps the key, so",
|
|
44875
|
-
" `failproofai jev setup --mode
|
|
44964
|
+
" `failproofai jev setup --mode observe` just switches the mode.",
|
|
44876
44965
|
"\u2022 The daemon does not see your shell's environment: keep the key in",
|
|
44877
44966
|
" the file on a machine set up with `failproofai config`.",
|
|
44878
44967
|
"\u2022 --token is the fast path, not the safe one: your shell history keeps",
|
|
@@ -44886,7 +44975,7 @@ Run \`failproofai backfill --help\` for usage.`);
|
|
|
44886
44975
|
"failproofai jev --url https://openrouter.ai/api/v1 --token <token>",
|
|
44887
44976
|
"failproofai jev setup --provider typesafe --key-stdin < ~/typesafe.key",
|
|
44888
44977
|
"failproofai jev setup --provider cloudflare --account-id <id> --key-stdin",
|
|
44889
|
-
"failproofai config --token <key> (FailproofAI Cloud,
|
|
44978
|
+
"failproofai config --token <key> (FailproofAI Cloud, observe mode)",
|
|
44890
44979
|
"failproofai jev setup --mode enforce (switch the configured route's mode)",
|
|
44891
44980
|
"failproofai jev setup --mode off (keep the config, stop asking Jev)",
|
|
44892
44981
|
"failproofai jev test",
|
|
@@ -45031,7 +45120,10 @@ Run \`failproofai migrate --help\` for usage.`);
|
|
|
45031
45120
|
"is the loud version of that problem rather than the silent one.",
|
|
45032
45121
|
"",
|
|
45033
45122
|
"This does the rest of the upgrade: runs any pending layout migrations,",
|
|
45034
|
-
"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."
|
|
45035
45127
|
]
|
|
45036
45128
|
},
|
|
45037
45129
|
{
|
|
@@ -45097,17 +45189,36 @@ Run \`failproofai update --help\` for usage.`);
|
|
|
45097
45189
|
daemonOk = result.ok;
|
|
45098
45190
|
}
|
|
45099
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;
|
|
45100
45209
|
await printReport("update", report, {
|
|
45101
|
-
ok:
|
|
45210
|
+
ok: updateOk,
|
|
45102
45211
|
meta: `v${version}`
|
|
45103
45212
|
});
|
|
45104
45213
|
await track("cli_update", {
|
|
45105
|
-
ok:
|
|
45214
|
+
ok: updateOk,
|
|
45106
45215
|
migrations: migrationsRan,
|
|
45107
|
-
migration_failed: migrationFailed
|
|
45216
|
+
migration_failed: migrationFailed,
|
|
45217
|
+
hermes_ok: hermesOk,
|
|
45218
|
+
hermes_migrated: hermesMigrated
|
|
45108
45219
|
});
|
|
45109
45220
|
lastSubcommand = null;
|
|
45110
|
-
await exitAfterFlush(
|
|
45221
|
+
await exitAfterFlush(updateOk ? 0 : 1);
|
|
45111
45222
|
return;
|
|
45112
45223
|
}
|
|
45113
45224
|
if (args[0] === "uninstall") {
|