failproofai 0.0.15-beta.0 → 0.0.15
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 +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- 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 +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +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 +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +20 -8
- 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/chunks/[root-of-the-server]__19120tr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1is3glr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +3 -3
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__04o-kw7._.js → [root-of-the-server]__06b4trb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0fopait._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +10 -10
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
- 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_audit_loading_tsx_1j1kc6j._.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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0w82veb.js → node_modules_html-to-image_es_index_0hs_5mh.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 +22 -10
- package/.next/standalone/.next/static/chunks/{31c084gsop1bb.js → 0kf9j0pf_j9-0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1qzsbft-jtnf8.js → 1-ng45379-ztb.js} +1 -1
- package/.next/standalone/.next/static/chunks/11qhtpk2cvoxv.js +1 -0
- package/.next/standalone/.next/static/chunks/{1_20f96f1253b.js → 1_v8b8mxhk25s.js} +1 -1
- package/.next/standalone/.next/static/chunks/{312ihvhxeuoi_.js → 2f4mxqkkoa_7d.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2xbkqlma_-61k.js → 2geh8s7d3ead-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3q9y67-fobgpc.js → 2h7vw-ojgr9fz.js} +1 -1
- package/.next/standalone/.next/static/chunks/33rm-y-i4uzvv.js +6 -0
- package/.next/standalone/.next/static/chunks/{1j2xm011_aoae.js → 33u59vf_8xpd-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2y7acsw0--8xf.js → 3m8wmvlhsy1mx.js} +1 -1
- package/.next/standalone/app/actions/get-hooks-config.ts +44 -12
- package/.next/standalone/app/actions/update-hooks-config.ts +38 -1
- package/.next/standalone/app/policies/hooks-client.tsx +38 -14
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +14 -13
- package/dist/cli.mjs +172 -97
- package/package.json +1 -1
- package/src/hooks/custom-hooks-loader.ts +33 -10
- package/src/hooks/handler.ts +8 -2
- package/src/hooks/hooks-config.ts +31 -5
- package/src/hooks/manager.ts +115 -50
- package/src/hooks/policy-types.ts +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1i21qhv._.js +0 -3
- package/.next/standalone/.next/static/chunks/0hn30_j22wpp_.js +0 -6
- package/.next/standalone/.next/static/chunks/3060a-0fayz96.js +0 -1
- /package/.next/standalone/.next/static/{QFmU1xD4R9N_HVfwccP6A → ePKMmXXfXxThNsxwv1EiF}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{QFmU1xD4R9N_HVfwccP6A → ePKMmXXfXxThNsxwv1EiF}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{QFmU1xD4R9N_HVfwccP6A → ePKMmXXfXxThNsxwv1EiF}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -19,7 +19,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
// package.json
|
|
22
|
-
var version2 = "0.0.15
|
|
22
|
+
var version2 = "0.0.15";
|
|
23
23
|
var init_package = () => {};
|
|
24
24
|
|
|
25
25
|
// src/posthog-key.ts
|
|
@@ -669,6 +669,9 @@ var init_hook_logger = __esm(() => {
|
|
|
669
669
|
import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, statSync as statSync2 } from "node:fs";
|
|
670
670
|
import { resolve, dirname } from "node:path";
|
|
671
671
|
import { homedir as homedir2 } from "node:os";
|
|
672
|
+
function configuredCustomPolicyPaths(config) {
|
|
673
|
+
return config.customPoliciesPaths ?? (config.customPoliciesPath ? [config.customPoliciesPath] : []);
|
|
674
|
+
}
|
|
672
675
|
function readConfigAt(path2) {
|
|
673
676
|
if (!existsSync2(path2))
|
|
674
677
|
return {};
|
|
@@ -719,12 +722,20 @@ function readMergedHooksConfig(cwd) {
|
|
|
719
722
|
}
|
|
720
723
|
}
|
|
721
724
|
}
|
|
722
|
-
const
|
|
725
|
+
const customPoliciesPaths = [project, local, global_].map((scope) => scope.customPoliciesPaths !== undefined ? scope.customPoliciesPaths : scope.customPoliciesPath !== undefined ? [scope.customPoliciesPath] : undefined).find((paths) => paths !== undefined);
|
|
726
|
+
const disabledCustomPolicies = new Set([
|
|
727
|
+
...project.disabledCustomPolicies ?? [],
|
|
728
|
+
...local.disabledCustomPolicies ?? [],
|
|
729
|
+
...global_.disabledCustomPolicies ?? []
|
|
730
|
+
]);
|
|
731
|
+
const customPoliciesEnabled = project.customPoliciesEnabled ?? local.customPoliciesEnabled ?? global_.customPoliciesEnabled;
|
|
723
732
|
const llm = project.llm ?? local.llm ?? global_.llm;
|
|
724
733
|
return {
|
|
725
734
|
enabledPolicies: [...enabledSet],
|
|
726
735
|
...Object.keys(mergedParams).length > 0 ? { policyParams: mergedParams } : {},
|
|
727
|
-
...
|
|
736
|
+
...customPoliciesPaths !== undefined ? { customPoliciesPaths } : {},
|
|
737
|
+
...disabledCustomPolicies.size ? { disabledCustomPolicies: [...disabledCustomPolicies] } : {},
|
|
738
|
+
...customPoliciesEnabled !== undefined ? { customPoliciesEnabled } : {},
|
|
728
739
|
...llm !== undefined ? { llm } : {}
|
|
729
740
|
};
|
|
730
741
|
}
|
|
@@ -3619,6 +3630,12 @@ async function loadCustomHooks(customPoliciesPath, opts) {
|
|
|
3619
3630
|
await loadSingleFile(absPath, opts);
|
|
3620
3631
|
return getCustomHooks();
|
|
3621
3632
|
}
|
|
3633
|
+
function customPolicyId(file, name) {
|
|
3634
|
+
return `custom:${file}:${name}`;
|
|
3635
|
+
}
|
|
3636
|
+
function conventionPolicyId(scope, file, name) {
|
|
3637
|
+
return `convention:${scope}:${file}:${name}`;
|
|
3638
|
+
}
|
|
3622
3639
|
function warnSkippedPolicyFiles(dir, scope) {
|
|
3623
3640
|
const skipped = findSkippedPolicyFiles(dir);
|
|
3624
3641
|
if (skipped.length === 0)
|
|
@@ -3626,19 +3643,25 @@ function warnSkippedPolicyFiles(dir, scope) {
|
|
|
3626
3643
|
const suggest = (n) => n.replace(/\.(js|mjs|ts)$/, "-policies.$1");
|
|
3627
3644
|
hookLogWarn(`${scope} policies: ${skipped.length} file(s) in ${dir} are NOT loaded — ` + `the convention only loads names ending in "policies.js|mjs|ts". ` + skipped.map((n) => `${n} → rename to ${suggest(n)}`).join("; "));
|
|
3628
3645
|
}
|
|
3629
|
-
async function loadAllCustomHooks(
|
|
3646
|
+
async function loadAllCustomHooks(customPoliciesPaths, opts) {
|
|
3630
3647
|
clearCustomHooks();
|
|
3631
3648
|
const conventionSources = [];
|
|
3632
3649
|
const projectRoot = findProjectConfigDir(opts?.sessionCwd ?? process.cwd());
|
|
3633
3650
|
const conventionEnabled = opts?.customPoliciesEnabled !== false;
|
|
3634
3651
|
const loadedPaths = new Set;
|
|
3635
|
-
|
|
3636
|
-
const absPath =
|
|
3652
|
+
for (const customPoliciesPath of typeof customPoliciesPaths === "string" ? [customPoliciesPaths] : customPoliciesPaths ?? []) {
|
|
3653
|
+
const absPath = resolve4(projectRoot, customPoliciesPath);
|
|
3637
3654
|
if (existsSync3(absPath)) {
|
|
3638
|
-
loadedPaths.
|
|
3639
|
-
|
|
3655
|
+
if (!loadedPaths.has(absPath)) {
|
|
3656
|
+
loadedPaths.add(absPath);
|
|
3657
|
+
const hooksBefore = getCustomHooks().length;
|
|
3658
|
+
await loadSingleFile(absPath);
|
|
3659
|
+
for (const hook of getCustomHooks().slice(hooksBefore)) {
|
|
3660
|
+
hook.__policyId = customPolicyId(absPath, hook.name);
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3640
3663
|
} else {
|
|
3641
|
-
hookLogWarn(`
|
|
3664
|
+
hookLogWarn(`custom policy path not found: ${absPath}`);
|
|
3642
3665
|
}
|
|
3643
3666
|
}
|
|
3644
3667
|
const hooksBeforeConvention = getCustomHooks().length;
|
|
@@ -3651,6 +3674,9 @@ async function loadAllCustomHooks(customPoliciesPath, opts) {
|
|
|
3651
3674
|
const hooksBefore = getCustomHooks().length;
|
|
3652
3675
|
await loadSingleFile(file, { conventionScope: "project" });
|
|
3653
3676
|
const newHooks = getCustomHooks().slice(hooksBefore);
|
|
3677
|
+
for (const hook of newHooks) {
|
|
3678
|
+
hook.__policyId = conventionPolicyId("project", basename(file), hook.name);
|
|
3679
|
+
}
|
|
3654
3680
|
if (newHooks.length > 0) {
|
|
3655
3681
|
conventionSources.push({
|
|
3656
3682
|
scope: "project",
|
|
@@ -3668,6 +3694,9 @@ async function loadAllCustomHooks(customPoliciesPath, opts) {
|
|
|
3668
3694
|
const hooksBefore = getCustomHooks().length;
|
|
3669
3695
|
await loadSingleFile(file, { conventionScope: "user" });
|
|
3670
3696
|
const newHooks = getCustomHooks().slice(hooksBefore);
|
|
3697
|
+
for (const hook of newHooks) {
|
|
3698
|
+
hook.__policyId = conventionPolicyId("user", basename(file), hook.name);
|
|
3699
|
+
}
|
|
3671
3700
|
if (newHooks.length > 0) {
|
|
3672
3701
|
conventionSources.push({
|
|
3673
3702
|
scope: "user",
|
|
@@ -8521,13 +8550,17 @@ async function handleHookEvent(eventType, cli = "claude") {
|
|
|
8521
8550
|
const config = readMergedHooksConfig(session.cwd);
|
|
8522
8551
|
clearPolicies();
|
|
8523
8552
|
registerBuiltinPolicies(config.enabledPolicies);
|
|
8524
|
-
const loadResult = await loadAllCustomHooks(config.customPoliciesPath, {
|
|
8553
|
+
const loadResult = await loadAllCustomHooks(config.customPoliciesPaths ?? config.customPoliciesPath, {
|
|
8525
8554
|
sessionCwd: session.cwd,
|
|
8526
8555
|
customPoliciesEnabled: config.customPoliciesEnabled
|
|
8527
8556
|
});
|
|
8528
8557
|
const customHooksList = loadResult.hooks;
|
|
8558
|
+
const disabledCustomPolicies = new Set(config.disabledCustomPolicies ?? []);
|
|
8529
8559
|
const conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
|
|
8530
8560
|
for (const hook of customHooksList) {
|
|
8561
|
+
const policyId = hook.__policyId;
|
|
8562
|
+
if (policyId && disabledCustomPolicies.has(policyId))
|
|
8563
|
+
continue;
|
|
8531
8564
|
const hookName = hook.name;
|
|
8532
8565
|
const conventionScope = hook.__conventionScope;
|
|
8533
8566
|
const isConvention = !!conventionScope;
|
|
@@ -18282,34 +18315,45 @@ async function installHooksImpl(policyNames, scope = "user", cwd, includeBeta =
|
|
|
18282
18315
|
const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
|
|
18283
18316
|
if (removeCustomHooks) {
|
|
18284
18317
|
delete configToWrite.customPoliciesPath;
|
|
18285
|
-
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
|
|
18289
|
-
|
|
18290
|
-
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18294
|
-
|
|
18295
|
-
|
|
18296
|
-
|
|
18297
|
-
|
|
18298
|
-
console.error(`Error: ${msg}`);
|
|
18299
|
-
process.exit(1);
|
|
18300
|
-
}
|
|
18301
|
-
if (validatedHooks.length === 0) {
|
|
18318
|
+
delete configToWrite.customPoliciesPaths;
|
|
18319
|
+
} else if (customPoliciesPath && (typeof customPoliciesPath === "string" || customPoliciesPath.length > 0)) {
|
|
18320
|
+
const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath).map((path3) => resolve12(path3));
|
|
18321
|
+
const carried = (previousConfig.customPoliciesPaths ?? (previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])).map((path3) => resolve12(path3)).filter((path3) => {
|
|
18322
|
+
if (existsSync12(path3))
|
|
18323
|
+
return true;
|
|
18324
|
+
console.log(`Dropping custom policies path (file no longer exists): ${path3}`);
|
|
18325
|
+
return false;
|
|
18326
|
+
});
|
|
18327
|
+
configToWrite.customPoliciesPaths = replace ? [...new Set(incoming)] : [...new Set([...carried, ...incoming])];
|
|
18328
|
+
delete configToWrite.customPoliciesPath;
|
|
18329
|
+
for (const path3 of incoming) {
|
|
18330
|
+
let validatedHooks = [];
|
|
18302
18331
|
try {
|
|
18303
|
-
await
|
|
18304
|
-
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
18309
|
-
|
|
18332
|
+
validatedHooks = await loadCustomHooks(path3, { strict: true });
|
|
18333
|
+
} catch (err) {
|
|
18334
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
18335
|
+
try {
|
|
18336
|
+
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
18337
|
+
scope,
|
|
18338
|
+
error_type: /not found/i.test(msg) ? "file_not_found" : "load_error"
|
|
18339
|
+
});
|
|
18340
|
+
} catch {}
|
|
18341
|
+
console.error(`Error: ${msg}`);
|
|
18342
|
+
process.exit(1);
|
|
18343
|
+
}
|
|
18344
|
+
if (validatedHooks.length === 0) {
|
|
18345
|
+
try {
|
|
18346
|
+
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
18347
|
+
scope,
|
|
18348
|
+
error_type: "no_hooks_registered"
|
|
18349
|
+
});
|
|
18350
|
+
} catch {}
|
|
18351
|
+
console.error(`Error: no hooks registered in ${path3}. ` + `Make sure your file calls customPolicies.add(...) at least once.`);
|
|
18352
|
+
process.exit(1);
|
|
18353
|
+
}
|
|
18354
|
+
console.log(`
|
|
18355
|
+
Validated ${validatedHooks.length} custom hook(s) from ${path3}: ${validatedHooks.map((h) => h.name).join(", ")}`);
|
|
18310
18356
|
}
|
|
18311
|
-
console.log(`
|
|
18312
|
-
Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
|
|
18313
18357
|
}
|
|
18314
18358
|
writeScopedHooksConfig(configToWrite, scope, cwd);
|
|
18315
18359
|
console.log(`
|
|
@@ -18317,8 +18361,9 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
|
|
|
18317
18361
|
`);
|
|
18318
18362
|
if (removeCustomHooks) {
|
|
18319
18363
|
console.log("Custom hooks path cleared.");
|
|
18320
|
-
} else if (configToWrite.customPoliciesPath) {
|
|
18321
|
-
|
|
18364
|
+
} else if (configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath) {
|
|
18365
|
+
const paths = configToWrite.customPoliciesPaths ?? [configToWrite.customPoliciesPath];
|
|
18366
|
+
console.log(`Custom hooks paths: ${paths.join(", ")}`);
|
|
18322
18367
|
}
|
|
18323
18368
|
const writtenSettingsPaths = [];
|
|
18324
18369
|
for (const cliId of selectedClis) {
|
|
@@ -18361,7 +18406,7 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
|
|
|
18361
18406
|
arch: arch3(),
|
|
18362
18407
|
os_release: release2(),
|
|
18363
18408
|
hostname_hash: hashToId2(hostname3()),
|
|
18364
|
-
has_custom_hooks_path: !!configToWrite.customPoliciesPath,
|
|
18409
|
+
has_custom_hooks_path: !!(configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath),
|
|
18365
18410
|
has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
|
|
18366
18411
|
param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
|
|
18367
18412
|
command_format: scope === "project" ? "npx" : "absolute"
|
|
@@ -18416,6 +18461,7 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
18416
18461
|
if (opts?.removeCustomHooks) {
|
|
18417
18462
|
const config = readScopedHooksConfig(configScope, cwd);
|
|
18418
18463
|
delete config.customPoliciesPath;
|
|
18464
|
+
delete config.customPoliciesPaths;
|
|
18419
18465
|
writeScopedHooksConfig(config, configScope, cwd);
|
|
18420
18466
|
console.log("Custom hooks path cleared.");
|
|
18421
18467
|
}
|
|
@@ -18521,20 +18567,21 @@ async function removeHooks(policyNames, scope = "user", cwd, opts) {
|
|
|
18521
18567
|
if (scope === "all") {
|
|
18522
18568
|
for (const s of HOOK_SCOPES) {
|
|
18523
18569
|
const existing = readScopedHooksConfig(s, cwd);
|
|
18524
|
-
if (existing.enabledPolicies.length > 0 || existing.customPoliciesPath || existing.policyParams) {
|
|
18525
|
-
const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
|
|
18570
|
+
if (existing.enabledPolicies.length > 0 || existing.customPoliciesPaths?.length || existing.customPoliciesPath || existing.policyParams) {
|
|
18571
|
+
const { customPoliciesPath: _drop, customPoliciesPaths: _dropMany, policyParams: _dropParams, ...rest } = existing;
|
|
18526
18572
|
writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, s, cwd);
|
|
18527
18573
|
}
|
|
18528
18574
|
}
|
|
18529
18575
|
} else if (!HOOK_SCOPES.some((s) => hooksInstalledInSettings(s, cwd))) {
|
|
18530
18576
|
const existing = readScopedHooksConfig(configScope, cwd);
|
|
18531
|
-
const { customPoliciesPath: _drop, policyParams: _dropParams, ...rest } = existing;
|
|
18577
|
+
const { customPoliciesPath: _drop, customPoliciesPaths: _dropMany, policyParams: _dropParams, ...rest } = existing;
|
|
18532
18578
|
writeScopedHooksConfig({ ...rest, enabledPolicies: [] }, configScope, cwd);
|
|
18533
18579
|
}
|
|
18534
18580
|
}
|
|
18535
18581
|
async function listHooks(cwd) {
|
|
18536
18582
|
const config = readMergedHooksConfig(cwd);
|
|
18537
18583
|
const enabledSet = new Set(config.enabledPolicies);
|
|
18584
|
+
const disabledCustomSet = new Set(config.disabledCustomPolicies ?? []);
|
|
18538
18585
|
const uniqueScopes = deduplicateScopes(HOOK_SCOPES, cwd);
|
|
18539
18586
|
const installedScopes = uniqueScopes.filter((s) => hooksInstalledInSettings(s, cwd));
|
|
18540
18587
|
const regularPolicies = BUILTIN_POLICIES.filter((p) => !p.beta);
|
|
@@ -18658,19 +18705,26 @@ Failproof AI Hook Policies
|
|
|
18658
18705
|
} catch {}
|
|
18659
18706
|
}
|
|
18660
18707
|
}
|
|
18661
|
-
|
|
18708
|
+
const explicitPaths = configuredCustomPolicyPaths(config);
|
|
18709
|
+
if (explicitPaths.length > 0) {
|
|
18662
18710
|
console.log(`
|
|
18663
|
-
── Custom Policies
|
|
18664
|
-
|
|
18665
|
-
|
|
18666
|
-
|
|
18667
|
-
|
|
18711
|
+
── Custom Policies ───────────────────────`);
|
|
18712
|
+
for (const path3 of explicitPaths) {
|
|
18713
|
+
const absPath = resolve12(findProjectConfigDir(cwd ?? process.cwd()), path3);
|
|
18714
|
+
console.log(` ${absPath}`);
|
|
18715
|
+
if (!existsSync12(absPath)) {
|
|
18716
|
+
console.log(` \x1B[31m✗ File not found: ${absPath}\x1B[0m`);
|
|
18717
|
+
continue;
|
|
18718
|
+
}
|
|
18719
|
+
const hooks = await loadCustomHooks(absPath);
|
|
18668
18720
|
if (hooks.length === 0) {
|
|
18669
18721
|
console.log(` \x1B[31m✗ ERR failed to load (check ~/.failproofai/logs/hooks.log)\x1B[0m`);
|
|
18670
18722
|
} else {
|
|
18671
18723
|
const descColWidth = nameColWidth;
|
|
18672
18724
|
for (const hook of hooks) {
|
|
18673
|
-
|
|
18725
|
+
const disabled = disabledCustomSet.has(`custom:${absPath}:${hook.name}`);
|
|
18726
|
+
const status = disabled ? "\x1B[2m OFF\x1B[0m" : "\x1B[32m✓ ON\x1B[0m";
|
|
18727
|
+
console.log(` ${status} ${hook.name.padEnd(descColWidth)}${hook.description ?? ""}`);
|
|
18674
18728
|
}
|
|
18675
18729
|
}
|
|
18676
18730
|
}
|
|
@@ -18693,6 +18747,7 @@ Failproof AI Hook Policies
|
|
|
18693
18747
|
if (files.length === 0)
|
|
18694
18748
|
continue;
|
|
18695
18749
|
const targets = dir === projectDir && dir === userDir ? ["project", "user"] : dir === projectDir ? ["project"] : ["user"];
|
|
18750
|
+
const policyScope = dir === projectDir ? "project" : "user";
|
|
18696
18751
|
const record = (file, hooks) => {
|
|
18697
18752
|
for (const t of targets)
|
|
18698
18753
|
discovered[t].push({ file, hooks });
|
|
@@ -18708,8 +18763,14 @@ Failproof AI Hook Policies
|
|
|
18708
18763
|
if (hooks.length === 0) {
|
|
18709
18764
|
console.log(` \x1B[31m✗\x1B[0m ${filename.padEnd(colWidth)}\x1B[31mfailed to load\x1B[0m`);
|
|
18710
18765
|
} else {
|
|
18711
|
-
const
|
|
18712
|
-
|
|
18766
|
+
const hookStates = hooks.map((hook) => ({
|
|
18767
|
+
hook,
|
|
18768
|
+
disabled: disabledCustomSet.has(`convention:${policyScope}:${filename}:${hook.name}`)
|
|
18769
|
+
}));
|
|
18770
|
+
const disabledCount = hookStates.filter((entry) => entry.disabled).length;
|
|
18771
|
+
const status = disabledCount === 0 ? "\x1B[32m✓ ON\x1B[0m" : disabledCount === hooks.length ? "\x1B[2m OFF\x1B[0m" : "\x1B[33m◐ MIXED\x1B[0m";
|
|
18772
|
+
const hookSummary = hookStates.map(({ hook, disabled }) => `${hook.name}${disabled ? " (OFF)" : ""}`).join(", ");
|
|
18773
|
+
console.log(` ${status} ${filename.padEnd(colWidth)}${hooks.length} hook(s): ${hookSummary}`);
|
|
18713
18774
|
}
|
|
18714
18775
|
} catch {
|
|
18715
18776
|
const filename = basename4(file);
|
|
@@ -22130,6 +22191,7 @@ WHAT IT DOES
|
|
|
22130
22191
|
|
|
22131
22192
|
// src/hooks/manager.ts
|
|
22132
22193
|
import { execSync as execSync7 } from "node:child_process";
|
|
22194
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
22133
22195
|
import { resolve as resolve16, basename as basename6 } from "node:path";
|
|
22134
22196
|
import { homedir as homedir29, platform as platform5, arch as arch4, release as release3, hostname as hostname4 } from "node:os";
|
|
22135
22197
|
function getSettingsPath2(scope, cwd) {
|
|
@@ -22236,34 +22298,45 @@ async function installHooksImpl2(policyNames, scope = "user", cwd, includeBeta =
|
|
|
22236
22298
|
const configToWrite = { ...previousConfig, enabledPolicies: selectedPolicies };
|
|
22237
22299
|
if (removeCustomHooks) {
|
|
22238
22300
|
delete configToWrite.customPoliciesPath;
|
|
22239
|
-
|
|
22240
|
-
|
|
22241
|
-
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
console.error(`Error: ${msg}`);
|
|
22253
|
-
process.exit(1);
|
|
22254
|
-
}
|
|
22255
|
-
if (validatedHooks.length === 0) {
|
|
22301
|
+
delete configToWrite.customPoliciesPaths;
|
|
22302
|
+
} else if (customPoliciesPath && (typeof customPoliciesPath === "string" || customPoliciesPath.length > 0)) {
|
|
22303
|
+
const incoming = (typeof customPoliciesPath === "string" ? [customPoliciesPath] : customPoliciesPath).map((path3) => resolve16(path3));
|
|
22304
|
+
const carried = (previousConfig.customPoliciesPaths ?? (previousConfig.customPoliciesPath ? [previousConfig.customPoliciesPath] : [])).map((path3) => resolve16(path3)).filter((path3) => {
|
|
22305
|
+
if (existsSync19(path3))
|
|
22306
|
+
return true;
|
|
22307
|
+
console.log(`Dropping custom policies path (file no longer exists): ${path3}`);
|
|
22308
|
+
return false;
|
|
22309
|
+
});
|
|
22310
|
+
configToWrite.customPoliciesPaths = replace ? [...new Set(incoming)] : [...new Set([...carried, ...incoming])];
|
|
22311
|
+
delete configToWrite.customPoliciesPath;
|
|
22312
|
+
for (const path3 of incoming) {
|
|
22313
|
+
let validatedHooks = [];
|
|
22256
22314
|
try {
|
|
22257
|
-
await
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22315
|
+
validatedHooks = await loadCustomHooks(path3, { strict: true });
|
|
22316
|
+
} catch (err) {
|
|
22317
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22318
|
+
try {
|
|
22319
|
+
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
22320
|
+
scope,
|
|
22321
|
+
error_type: /not found/i.test(msg) ? "file_not_found" : "load_error"
|
|
22322
|
+
});
|
|
22323
|
+
} catch {}
|
|
22324
|
+
console.error(`Error: ${msg}`);
|
|
22325
|
+
process.exit(1);
|
|
22326
|
+
}
|
|
22327
|
+
if (validatedHooks.length === 0) {
|
|
22328
|
+
try {
|
|
22329
|
+
await trackHookEvent2(getInstanceId2(), "custom_policy_validation_failed", {
|
|
22330
|
+
scope,
|
|
22331
|
+
error_type: "no_hooks_registered"
|
|
22332
|
+
});
|
|
22333
|
+
} catch {}
|
|
22334
|
+
console.error(`Error: no hooks registered in ${path3}. ` + `Make sure your file calls customPolicies.add(...) at least once.`);
|
|
22335
|
+
process.exit(1);
|
|
22336
|
+
}
|
|
22337
|
+
console.log(`
|
|
22338
|
+
Validated ${validatedHooks.length} custom hook(s) from ${path3}: ${validatedHooks.map((h) => h.name).join(", ")}`);
|
|
22264
22339
|
}
|
|
22265
|
-
console.log(`
|
|
22266
|
-
Validated ${validatedHooks.length} custom hook(s): ${validatedHooks.map((h) => h.name).join(", ")}`);
|
|
22267
22340
|
}
|
|
22268
22341
|
writeScopedHooksConfig(configToWrite, scope, cwd);
|
|
22269
22342
|
console.log(`
|
|
@@ -22271,8 +22344,9 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
|
|
|
22271
22344
|
`);
|
|
22272
22345
|
if (removeCustomHooks) {
|
|
22273
22346
|
console.log("Custom hooks path cleared.");
|
|
22274
|
-
} else if (configToWrite.customPoliciesPath) {
|
|
22275
|
-
|
|
22347
|
+
} else if (configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath) {
|
|
22348
|
+
const paths = configToWrite.customPoliciesPaths ?? [configToWrite.customPoliciesPath];
|
|
22349
|
+
console.log(`Custom hooks paths: ${paths.join(", ")}`);
|
|
22276
22350
|
}
|
|
22277
22351
|
const writtenSettingsPaths = [];
|
|
22278
22352
|
for (const cliId of selectedClis) {
|
|
@@ -22315,7 +22389,7 @@ Enabled ${selectedPolicies.length} policy(ies): ${selectedPolicies.join(", ")}
|
|
|
22315
22389
|
arch: arch4(),
|
|
22316
22390
|
os_release: release3(),
|
|
22317
22391
|
hostname_hash: hashToId2(hostname4()),
|
|
22318
|
-
has_custom_hooks_path: !!configToWrite.customPoliciesPath,
|
|
22392
|
+
has_custom_hooks_path: !!(configToWrite.customPoliciesPaths?.length || configToWrite.customPoliciesPath),
|
|
22319
22393
|
has_policy_params: !!(configToWrite.policyParams && Object.keys(configToWrite.policyParams).length > 0),
|
|
22320
22394
|
param_policy_names: configToWrite.policyParams ? Object.keys(configToWrite.policyParams) : [],
|
|
22321
22395
|
command_format: scope === "project" ? "npx" : "absolute"
|
|
@@ -22714,7 +22788,7 @@ __export(exports_configure_wizard, {
|
|
|
22714
22788
|
buildPresetChoices: () => buildPresetChoices,
|
|
22715
22789
|
buildAgentChoices: () => buildAgentChoices
|
|
22716
22790
|
});
|
|
22717
|
-
import { existsSync as
|
|
22791
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync16 } from "node:fs";
|
|
22718
22792
|
import { homedir as homedir30 } from "node:os";
|
|
22719
22793
|
import { resolve as resolve17, sep as sep5 } from "node:path";
|
|
22720
22794
|
async function emit(event, props) {
|
|
@@ -22837,7 +22911,7 @@ function setCustomPoliciesEnabled(scope, cwd, enabled) {
|
|
|
22837
22911
|
const path3 = getConfigPathForScope(scope, cwd);
|
|
22838
22912
|
let config = {};
|
|
22839
22913
|
try {
|
|
22840
|
-
if (
|
|
22914
|
+
if (existsSync20(path3))
|
|
22841
22915
|
config = JSON.parse(readFileSync16(path3, "utf8"));
|
|
22842
22916
|
} catch {
|
|
22843
22917
|
return;
|
|
@@ -22902,7 +22976,7 @@ function firstRunMarkerPath() {
|
|
|
22902
22976
|
return resolve17(homedir30(), ".failproofai", ".launcher-configured");
|
|
22903
22977
|
}
|
|
22904
22978
|
function hasSeenLauncher() {
|
|
22905
|
-
return
|
|
22979
|
+
return existsSync20(firstRunMarkerPath());
|
|
22906
22980
|
}
|
|
22907
22981
|
function markLauncherSeen() {
|
|
22908
22982
|
try {
|
|
@@ -23056,7 +23130,7 @@ __export(exports_launch2, {
|
|
|
23056
23130
|
launch: () => launch2
|
|
23057
23131
|
});
|
|
23058
23132
|
import { spawn as spawn3 } from "child_process";
|
|
23059
|
-
import { realpathSync as realpathSync3, existsSync as
|
|
23133
|
+
import { realpathSync as realpathSync3, existsSync as existsSync21 } from "node:fs";
|
|
23060
23134
|
import { resolve as resolve18, dirname as dirname9 } from "node:path";
|
|
23061
23135
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
23062
23136
|
import { createInterface as createInterface3 } from "node:readline";
|
|
@@ -23074,7 +23148,7 @@ function launch2(mode) {
|
|
|
23074
23148
|
cmd = "node";
|
|
23075
23149
|
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve18(dirname9(realpathSync3(fileURLToPath3(import.meta.url))), "..");
|
|
23076
23150
|
const serverJsPath = resolve18(packageRoot, ".next/standalone/server.js");
|
|
23077
|
-
if (!
|
|
23151
|
+
if (!existsSync21(serverJsPath)) {
|
|
23078
23152
|
let shadowMessage = null;
|
|
23079
23153
|
try {
|
|
23080
23154
|
const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
|
|
@@ -23172,7 +23246,7 @@ import { realpathSync as realpathSync4 } from "fs";
|
|
|
23172
23246
|
import { dirname as dirname10, resolve as resolve19 } from "path";
|
|
23173
23247
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
23174
23248
|
// package.json
|
|
23175
|
-
var version = "0.0.15
|
|
23249
|
+
var version = "0.0.15";
|
|
23176
23250
|
|
|
23177
23251
|
// bin/failproofai.mjs
|
|
23178
23252
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
@@ -23266,7 +23340,7 @@ COMMANDS
|
|
|
23266
23340
|
--scope user|project|local Config scope to write to (default: user)
|
|
23267
23341
|
(Codex / Copilot / Cursor / OpenCode / Pi support user|project only)
|
|
23268
23342
|
--beta Include beta policies
|
|
23269
|
-
--custom, -c <path>
|
|
23343
|
+
--custom, -c <path> Custom policy file (repeat for multiple files)
|
|
23270
23344
|
|
|
23271
23345
|
policies --uninstall, -u Disable policies or remove hooks
|
|
23272
23346
|
[names...] Specific policy names to disable
|
|
@@ -23274,7 +23348,7 @@ COMMANDS
|
|
|
23274
23348
|
Agent CLI(s) to uninstall from
|
|
23275
23349
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
23276
23350
|
--beta Remove only beta policies
|
|
23277
|
-
--custom, -c Clear
|
|
23351
|
+
--custom, -c Clear all explicit custom policy paths
|
|
23278
23352
|
|
|
23279
23353
|
policies --help, -h Show this help for the policies command
|
|
23280
23354
|
|
|
@@ -23358,7 +23432,7 @@ OPTIONS (install)
|
|
|
23358
23432
|
--scope user|project|local Config scope to write to (default: user)
|
|
23359
23433
|
(Codex / Copilot / Cursor / OpenCode / Pi support user|project only)
|
|
23360
23434
|
--beta Include beta policies
|
|
23361
|
-
--custom, -c <path>
|
|
23435
|
+
--custom, -c <path> Custom policy file (repeat for multiple files)
|
|
23362
23436
|
(skips interactive prompt; validates file first)
|
|
23363
23437
|
|
|
23364
23438
|
OPTIONS (uninstall)
|
|
@@ -23367,7 +23441,7 @@ OPTIONS (uninstall)
|
|
|
23367
23441
|
Agent CLI(s) to uninstall from
|
|
23368
23442
|
--scope user|project|local|all Config scope to remove from (default: user)
|
|
23369
23443
|
--beta Remove only beta policies
|
|
23370
|
-
--custom, -c Clear
|
|
23444
|
+
--custom, -c Clear all explicit custom policy paths
|
|
23371
23445
|
|
|
23372
23446
|
EXAMPLES
|
|
23373
23447
|
failproofai policies
|
|
@@ -23382,6 +23456,7 @@ EXAMPLES
|
|
|
23382
23456
|
failproofai policies --install --cli devin --scope project
|
|
23383
23457
|
failproofai policies --install --cli claude codex copilot cursor opencode pi hermes openclaw factory devin antigravity goose
|
|
23384
23458
|
failproofai policies --install --custom ./my-policies.js
|
|
23459
|
+
failproofai policies --install --custom ./security.js --custom ./workflow.js
|
|
23385
23460
|
failproofai policies -i -c ./my-policies.js
|
|
23386
23461
|
failproofai policies --uninstall block-sudo
|
|
23387
23462
|
failproofai policies --uninstall --cli codex
|
|
@@ -23406,9 +23481,9 @@ EXAMPLES
|
|
|
23406
23481
|
if (scopeIdx >= 0 && !["user", "project", "local"].includes(scope)) {
|
|
23407
23482
|
throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local`);
|
|
23408
23483
|
}
|
|
23409
|
-
const
|
|
23410
|
-
const
|
|
23411
|
-
if (
|
|
23484
|
+
const customIdxs = subArgs.map((arg, index) => arg === "--custom" || arg === "-c" ? index : -1).filter((index) => index >= 0);
|
|
23485
|
+
const customPoliciesPaths = customIdxs.map((index) => subArgs[index + 1]);
|
|
23486
|
+
if (customPoliciesPaths.some((path3) => !path3 || path3.startsWith("-"))) {
|
|
23412
23487
|
throw new CliError3(`Missing path after --custom/-c
|
|
23413
23488
|
Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
|
|
23414
23489
|
}
|
|
@@ -23436,7 +23511,7 @@ Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
|
|
|
23436
23511
|
const consumedIdxs = new Set;
|
|
23437
23512
|
if (scopeIdx >= 0)
|
|
23438
23513
|
consumedIdxs.add(scopeIdx + 1);
|
|
23439
|
-
|
|
23514
|
+
for (const customIdx of customIdxs)
|
|
23440
23515
|
consumedIdxs.add(customIdx + 1);
|
|
23441
23516
|
for (const i of cliConsumedIdxs)
|
|
23442
23517
|
consumedIdxs.add(i);
|
|
@@ -23447,16 +23522,16 @@ Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
|
|
|
23447
23522
|
Run \`failproofai policies --help\` for usage.`);
|
|
23448
23523
|
}
|
|
23449
23524
|
const explicitPolicyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
|
|
23450
|
-
const policyNames = explicitPolicyNames.length > 0 ? explicitPolicyNames :
|
|
23525
|
+
const policyNames = explicitPolicyNames.length > 0 ? explicitPolicyNames : customPoliciesPaths.length > 0 ? [] : undefined;
|
|
23451
23526
|
const cli = await resolveTargetClis2(cliFlagValues.length > 0 ? cliFlagValues : undefined, "install");
|
|
23452
|
-
await installHooks3(policyNames, scope, undefined, includeBeta, undefined,
|
|
23527
|
+
await installHooks3(policyNames, scope, undefined, includeBeta, undefined, customPoliciesPaths.length > 0 ? customPoliciesPaths : undefined, false, cli);
|
|
23453
23528
|
await track("cli_install_success", {
|
|
23454
23529
|
scope,
|
|
23455
23530
|
cli,
|
|
23456
23531
|
cli_count: cli.length,
|
|
23457
23532
|
explicit_policies: explicitPolicyNames.length > 0,
|
|
23458
23533
|
include_beta: includeBeta,
|
|
23459
|
-
has_custom_path:
|
|
23534
|
+
has_custom_path: customPoliciesPaths.length > 0
|
|
23460
23535
|
});
|
|
23461
23536
|
process.exit(0);
|
|
23462
23537
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.15
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Supports transitive local imports and `import { ... } from 'failproofai'`.
|
|
4
4
|
*
|
|
5
5
|
* Two loading modes:
|
|
6
|
-
* 1. Explicit:
|
|
6
|
+
* 1. Explicit: files via `customPoliciesPaths` in policies-config.json
|
|
7
7
|
* 2. Convention: auto-discovered *policies.{js,mjs,ts} files from
|
|
8
8
|
* .failproofai/policies/ at project and user level (git-hooks style)
|
|
9
9
|
*
|
|
@@ -177,6 +177,14 @@ export interface LoadAllResult {
|
|
|
177
177
|
conventionSources: ConventionSource[];
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
export function customPolicyId(file: string, name: string): string {
|
|
181
|
+
return `custom:${file}:${name}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function conventionPolicyId(scope: "project" | "user", file: string, name: string): string {
|
|
185
|
+
return `convention:${scope}:${file}:${name}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
180
188
|
/**
|
|
181
189
|
* Load ALL custom hooks: explicit customPoliciesPath + convention-discovered files.
|
|
182
190
|
*
|
|
@@ -205,7 +213,7 @@ function warnSkippedPolicyFiles(dir: string, scope: "project" | "user"): void {
|
|
|
205
213
|
}
|
|
206
214
|
|
|
207
215
|
export async function loadAllCustomHooks(
|
|
208
|
-
|
|
216
|
+
customPoliciesPaths: string | string[] | undefined,
|
|
209
217
|
opts?: { sessionCwd?: string; customPoliciesEnabled?: boolean },
|
|
210
218
|
): Promise<LoadAllResult> {
|
|
211
219
|
clearCustomHooks();
|
|
@@ -228,16 +236,25 @@ export async function loadAllCustomHooks(
|
|
|
228
236
|
// convention passes.
|
|
229
237
|
const loadedPaths = new Set<string>();
|
|
230
238
|
|
|
231
|
-
// 1. Explicit
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
239
|
+
// 1. Explicit custom policy paths. Accept a string for callers/configs using
|
|
240
|
+
// the legacy singular form.
|
|
241
|
+
for (const customPoliciesPath of typeof customPoliciesPaths === "string"
|
|
242
|
+
? [customPoliciesPaths]
|
|
243
|
+
: customPoliciesPaths ?? []) {
|
|
244
|
+
// resolve() also normalizes absolute paths, so aliases containing `.` or
|
|
245
|
+
// `..` share a dedup key with convention-discovered canonical paths.
|
|
246
|
+
const absPath = resolve(projectRoot, customPoliciesPath);
|
|
236
247
|
if (existsSync(absPath)) {
|
|
237
|
-
loadedPaths.
|
|
238
|
-
|
|
248
|
+
if (!loadedPaths.has(absPath)) {
|
|
249
|
+
loadedPaths.add(absPath);
|
|
250
|
+
const hooksBefore = getCustomHooks().length;
|
|
251
|
+
await loadSingleFile(absPath);
|
|
252
|
+
for (const hook of getCustomHooks().slice(hooksBefore)) {
|
|
253
|
+
(hook as CustomHook & { __policyId?: string }).__policyId = customPolicyId(absPath, hook.name);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
239
256
|
} else {
|
|
240
|
-
hookLogWarn(`
|
|
257
|
+
hookLogWarn(`custom policy path not found: ${absPath}`);
|
|
241
258
|
}
|
|
242
259
|
}
|
|
243
260
|
|
|
@@ -260,6 +277,9 @@ export async function loadAllCustomHooks(
|
|
|
260
277
|
const hooksBefore = getCustomHooks().length;
|
|
261
278
|
await loadSingleFile(file, { conventionScope: "project" });
|
|
262
279
|
const newHooks = getCustomHooks().slice(hooksBefore);
|
|
280
|
+
for (const hook of newHooks) {
|
|
281
|
+
(hook as CustomHook & { __policyId?: string }).__policyId = conventionPolicyId("project", basename(file), hook.name);
|
|
282
|
+
}
|
|
263
283
|
if (newHooks.length > 0) {
|
|
264
284
|
conventionSources.push({
|
|
265
285
|
scope: "project",
|
|
@@ -294,6 +314,9 @@ export async function loadAllCustomHooks(
|
|
|
294
314
|
const hooksBefore = getCustomHooks().length;
|
|
295
315
|
await loadSingleFile(file, { conventionScope: "user" });
|
|
296
316
|
const newHooks = getCustomHooks().slice(hooksBefore);
|
|
317
|
+
for (const hook of newHooks) {
|
|
318
|
+
(hook as CustomHook & { __policyId?: string }).__policyId = conventionPolicyId("user", basename(file), hook.name);
|
|
319
|
+
}
|
|
297
320
|
if (newHooks.length > 0) {
|
|
298
321
|
conventionSources.push({
|
|
299
322
|
scope: "user",
|