failproofai 0.0.2-beta.1 → 0.0.2-beta.3
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/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 +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
- 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 +11 -11
- 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 +2 -2
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
- 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 +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +1 -1
- 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]__02nt~6d._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0a3kr67._.js → [root-of-the-server]__07k6eu-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rbuarm._.js → [root-of-the-server]__0kfv9fw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0osi8nq._.js → [root-of-the-server]__0okos0k._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +5 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0rd0oc-._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +2 -2
- 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 +9 -9
- package/.next/standalone/.next/static/chunks/{0a08gn8709y98.js → 0.jo.465b6_k..js} +1 -1
- package/.next/standalone/.next/static/chunks/{0jhw8ofx.5g_e.js → 01haq0a3zrx0v.js} +1 -1
- package/.next/standalone/.next/static/chunks/08f78tecvx61l.css +1 -0
- package/.next/standalone/.next/static/chunks/{0mr-jhx402yci.js → 0a6xi1a8f_qlp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0qvj8bhl661lq.js → 0mq7ze1vkeo1p.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0gcz-jqgqz~9m.js → 0p_fpyfmmohnx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0kob_5.phc~sk.js → 0qwyj3m400l_g.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0mjc3aq2wxvlt.js → 0t94r_mk0s7e4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0q7z97izctgrw.js → 139~00zc9.u7s.js} +1 -1
- package/.next/standalone/Dockerfile.docs +12 -0
- package/.next/standalone/README.md +68 -55
- package/.next/standalone/bin/failproofai.mjs +221 -128
- package/.next/standalone/dist/cli.mjs +415 -106
- package/.next/standalone/dist/index.js +2 -2
- package/.next/standalone/docs/{architecture.md → architecture.mdx} +40 -23
- package/.next/standalone/docs/{built-in-policies.md → built-in-policies.mdx} +134 -12
- package/.next/standalone/docs/cli/dashboard.mdx +28 -0
- package/.next/standalone/docs/cli/environment-variables.mdx +34 -0
- package/.next/standalone/docs/cli/hook.mdx +30 -0
- package/.next/standalone/docs/cli/install-policies.mdx +48 -0
- package/.next/standalone/docs/cli/list-policies.mdx +31 -0
- package/.next/standalone/docs/cli/remove-policies.mdx +44 -0
- package/.next/standalone/docs/cli/version.mdx +12 -0
- package/.next/standalone/docs/{configuration.md → configuration.mdx} +16 -16
- package/.next/standalone/docs/{custom-hooks.md → custom-policies.mdx} +80 -42
- package/.next/standalone/docs/{dashboard.md → dashboard.mdx} +26 -29
- package/.next/standalone/docs/docs.json +31 -4
- package/.next/standalone/docs/examples.mdx +253 -0
- package/.next/standalone/docs/for-agents.mdx +38 -0
- package/.next/standalone/docs/getting-started.mdx +134 -0
- package/.next/standalone/docs/introduction.mdx +57 -0
- package/.next/standalone/docs/logo/dark.svg +21 -0
- package/.next/standalone/docs/logo/light.svg +21 -0
- package/.next/standalone/docs/{package-aliases.md → package-aliases.mdx} +5 -5
- package/.next/standalone/docs/{testing.md → testing.mdx} +11 -11
- package/.next/standalone/package.json +6 -9
- package/.next/standalone/scripts/publish-aliases.mjs +4 -2
- package/.next/standalone/skills-lock.json +10 -0
- package/.next/standalone/src/cli-error.ts +18 -0
- package/.next/standalone/src/hooks/builtin-policies.ts +259 -20
- package/.next/standalone/src/hooks/manager.ts +17 -3
- package/.next/standalone/src/hooks/policy-evaluator.ts +19 -1
- package/.next/standalone/src/hooks/policy-helpers.ts +2 -2
- package/.next/standalone/vitest.config.e2e.mts +3 -0
- package/.next/standalone/vitest.config.mts +3 -0
- package/README.md +68 -55
- package/bin/failproofai.mjs +221 -128
- package/dist/cli.mjs +415 -106
- package/dist/index.js +2 -2
- package/package.json +6 -9
- package/scripts/publish-aliases.mjs +4 -2
- package/src/cli-error.ts +18 -0
- package/src/hooks/builtin-policies.ts +259 -20
- package/src/hooks/manager.ts +17 -3
- package/src/hooks/policy-evaluator.ts +19 -1
- package/src/hooks/policy-helpers.ts +2 -2
- package/.next/standalone/.next/static/chunks/15jpradyu_531.css +0 -1
- package/.next/standalone/docs/cli-reference.md +0 -175
- package/.next/standalone/docs/getting-started.md +0 -128
- package/.next/standalone/docs/introduction.md +0 -47
- /package/.next/standalone/.next/static/{Dnk96sbMPjYOx1pdLdOH0 → 7fR022u1Sj-s5MfKO1q9Y}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{Dnk96sbMPjYOx1pdLdOH0 → 7fR022u1Sj-s5MfKO1q9Y}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{Dnk96sbMPjYOx1pdLdOH0 → 7fR022u1Sj-s5MfKO1q9Y}/_ssgManifest.js +0 -0
|
@@ -179,8 +179,8 @@ var init_hooks_config = __esm(() => {
|
|
|
179
179
|
});
|
|
180
180
|
|
|
181
181
|
// src/hooks/policy-helpers.ts
|
|
182
|
-
function allow() {
|
|
183
|
-
return { decision: "allow" };
|
|
182
|
+
function allow(reason) {
|
|
183
|
+
return reason ? { decision: "allow", reason } : { decision: "allow" };
|
|
184
184
|
}
|
|
185
185
|
function deny(reason) {
|
|
186
186
|
return { decision: "deny", reason };
|
|
@@ -248,7 +248,7 @@ var REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__", INDEX_CACHE_KEY = "__FAILP
|
|
|
248
248
|
// src/hooks/builtin-policies.ts
|
|
249
249
|
import { resolve as resolve2, join as join2 } from "node:path";
|
|
250
250
|
import { readFile, writeFile } from "node:fs/promises";
|
|
251
|
-
import { execSync } from "node:child_process";
|
|
251
|
+
import { execSync, execFileSync } from "node:child_process";
|
|
252
252
|
import { homedir as homedir3 } from "node:os";
|
|
253
253
|
function isClaudeInternalPath(resolved2) {
|
|
254
254
|
const claudeDir = join2(homedir3(), ".claude");
|
|
@@ -266,6 +266,22 @@ function getFilePath(ctx) {
|
|
|
266
266
|
function parseArgvTokens(cmd) {
|
|
267
267
|
return cmd.trim().split(/\s+/).map((t) => t.replace(/^['"]|['"]$/g, ""));
|
|
268
268
|
}
|
|
269
|
+
function getCurrentBranch(cwd) {
|
|
270
|
+
try {
|
|
271
|
+
let branch = gitBranchCache.get(cwd);
|
|
272
|
+
if (branch === undefined) {
|
|
273
|
+
branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
274
|
+
cwd,
|
|
275
|
+
encoding: "utf8",
|
|
276
|
+
timeout: 3000
|
|
277
|
+
}).trim();
|
|
278
|
+
gitBranchCache.set(cwd, branch);
|
|
279
|
+
}
|
|
280
|
+
return branch || null;
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
269
285
|
function matchesAllowedPattern(cmd, pattern) {
|
|
270
286
|
const cmdTokens = parseArgvTokens(cmd);
|
|
271
287
|
const patTokens = parseArgvTokens(pattern);
|
|
@@ -653,22 +669,12 @@ function blockWorkOnMain(ctx) {
|
|
|
653
669
|
const cwd = ctx.session?.cwd;
|
|
654
670
|
if (!cwd)
|
|
655
671
|
return allow();
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if (branch === undefined) {
|
|
659
|
-
branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
660
|
-
cwd,
|
|
661
|
-
encoding: "utf8",
|
|
662
|
-
timeout: 3000
|
|
663
|
-
}).trim();
|
|
664
|
-
gitBranchCache.set(cwd, branch);
|
|
665
|
-
}
|
|
666
|
-
const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
|
|
667
|
-
if (protectedBranches.includes(branch)) {
|
|
668
|
-
return deny(`Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`);
|
|
669
|
-
}
|
|
670
|
-
} catch {
|
|
672
|
+
const branch = getCurrentBranch(cwd);
|
|
673
|
+
if (!branch)
|
|
671
674
|
return allow();
|
|
675
|
+
const protectedBranches = ctx.params?.protectedBranches ?? ["main", "master"];
|
|
676
|
+
if (protectedBranches.includes(branch)) {
|
|
677
|
+
return deny(`Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`);
|
|
672
678
|
}
|
|
673
679
|
return allow();
|
|
674
680
|
}
|
|
@@ -767,6 +773,137 @@ function warnBackgroundProcess(ctx) {
|
|
|
767
773
|
}
|
|
768
774
|
return allow();
|
|
769
775
|
}
|
|
776
|
+
function requireCommitBeforeStop(ctx) {
|
|
777
|
+
const cwd = ctx.session?.cwd;
|
|
778
|
+
if (!cwd)
|
|
779
|
+
return allow("No working directory available, skipping commit check.");
|
|
780
|
+
try {
|
|
781
|
+
const status = execSync("git status --porcelain", {
|
|
782
|
+
cwd,
|
|
783
|
+
encoding: "utf8",
|
|
784
|
+
timeout: 5000
|
|
785
|
+
}).trim();
|
|
786
|
+
if (status.length > 0) {
|
|
787
|
+
return deny("You have uncommitted changes in the working directory. Commit all changes before stopping.");
|
|
788
|
+
}
|
|
789
|
+
return allow("All changes are committed.");
|
|
790
|
+
} catch {
|
|
791
|
+
return allow("Not a git repository, skipping commit check.");
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
function requirePushBeforeStop(ctx) {
|
|
795
|
+
const cwd = ctx.session?.cwd;
|
|
796
|
+
if (!cwd)
|
|
797
|
+
return allow("No working directory available, skipping push check.");
|
|
798
|
+
try {
|
|
799
|
+
const remotes = execSync("git remote", {
|
|
800
|
+
cwd,
|
|
801
|
+
encoding: "utf8",
|
|
802
|
+
timeout: 3000
|
|
803
|
+
}).trim();
|
|
804
|
+
if (!remotes)
|
|
805
|
+
return allow("No git remote configured, skipping push check.");
|
|
806
|
+
const remote = ctx.params?.remote ?? "origin";
|
|
807
|
+
const branch = getCurrentBranch(cwd);
|
|
808
|
+
if (!branch || branch === "HEAD")
|
|
809
|
+
return allow("Detached HEAD, skipping push check.");
|
|
810
|
+
let hasTracking = false;
|
|
811
|
+
try {
|
|
812
|
+
execFileSync("git", ["rev-parse", "--verify", `${remote}/${branch}`], {
|
|
813
|
+
cwd,
|
|
814
|
+
encoding: "utf8",
|
|
815
|
+
timeout: 3000
|
|
816
|
+
});
|
|
817
|
+
hasTracking = true;
|
|
818
|
+
} catch {}
|
|
819
|
+
if (!hasTracking) {
|
|
820
|
+
return deny(`Branch "${branch}" has not been pushed to remote "${remote}". ` + `Push your branch with: git push -u ${remote} ${branch}`);
|
|
821
|
+
}
|
|
822
|
+
const unpushed = execFileSync("git", ["log", `${remote}/${branch}..HEAD`, "--oneline"], {
|
|
823
|
+
cwd,
|
|
824
|
+
encoding: "utf8",
|
|
825
|
+
timeout: 5000
|
|
826
|
+
}).trim();
|
|
827
|
+
if (unpushed.length > 0) {
|
|
828
|
+
const commitCount = unpushed.split(`
|
|
829
|
+
`).length;
|
|
830
|
+
return deny(`You have ${commitCount} unpushed commit${commitCount > 1 ? "s" : ""} on branch "${branch}". ` + `Push your changes with: git push`);
|
|
831
|
+
}
|
|
832
|
+
return allow(`All commits pushed to "${remote}".`);
|
|
833
|
+
} catch {
|
|
834
|
+
return allow("Could not check push status, skipping.");
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function requirePrBeforeStop(ctx) {
|
|
838
|
+
const cwd = ctx.session?.cwd;
|
|
839
|
+
if (!cwd)
|
|
840
|
+
return allow("No working directory available, skipping PR check.");
|
|
841
|
+
try {
|
|
842
|
+
try {
|
|
843
|
+
execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
|
|
844
|
+
} catch {
|
|
845
|
+
return allow("GitHub CLI (gh) not installed, skipping PR check.");
|
|
846
|
+
}
|
|
847
|
+
const branch = getCurrentBranch(cwd);
|
|
848
|
+
if (!branch || branch === "HEAD")
|
|
849
|
+
return allow("Detached HEAD, skipping PR check.");
|
|
850
|
+
let prJson;
|
|
851
|
+
try {
|
|
852
|
+
prJson = execSync("gh pr view --json number,url,state", {
|
|
853
|
+
cwd,
|
|
854
|
+
encoding: "utf8",
|
|
855
|
+
timeout: 15000
|
|
856
|
+
}).trim();
|
|
857
|
+
} catch {
|
|
858
|
+
return deny(`No pull request found for branch "${branch}". ` + `Create one with: gh pr create`);
|
|
859
|
+
}
|
|
860
|
+
const pr = JSON.parse(prJson);
|
|
861
|
+
if (pr.state === "OPEN") {
|
|
862
|
+
return allow(`PR #${pr.number} exists: ${pr.url}`);
|
|
863
|
+
}
|
|
864
|
+
return deny(`Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Create a new PR with: gh pr create`);
|
|
865
|
+
} catch {
|
|
866
|
+
return allow("Could not check PR status, skipping.");
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
function requireCiGreenBeforeStop(ctx) {
|
|
870
|
+
const cwd = ctx.session?.cwd;
|
|
871
|
+
if (!cwd)
|
|
872
|
+
return allow("No working directory available, skipping CI check.");
|
|
873
|
+
try {
|
|
874
|
+
try {
|
|
875
|
+
execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
|
|
876
|
+
} catch {
|
|
877
|
+
return allow("GitHub CLI (gh) not installed, skipping CI check.");
|
|
878
|
+
}
|
|
879
|
+
const branch = getCurrentBranch(cwd);
|
|
880
|
+
if (!branch || branch === "HEAD")
|
|
881
|
+
return allow("Detached HEAD, skipping CI check.");
|
|
882
|
+
const runsJson = execFileSync("gh", ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"], {
|
|
883
|
+
cwd,
|
|
884
|
+
encoding: "utf8",
|
|
885
|
+
timeout: 15000
|
|
886
|
+
}).trim();
|
|
887
|
+
if (!runsJson || runsJson === "[]")
|
|
888
|
+
return allow(`No CI runs found for branch "${branch}".`);
|
|
889
|
+
const runs = JSON.parse(runsJson);
|
|
890
|
+
if (runs.length === 0)
|
|
891
|
+
return allow(`No CI runs found for branch "${branch}".`);
|
|
892
|
+
const failing = runs.filter((r) => r.status === "completed" && r.conclusion !== "success" && r.conclusion !== "skipped");
|
|
893
|
+
if (failing.length > 0) {
|
|
894
|
+
const names = failing.map((r) => `"${r.name}"`).join(", ");
|
|
895
|
+
return deny(`CI checks are failing on branch "${branch}": ${names}. Fix the failing checks before stopping.`);
|
|
896
|
+
}
|
|
897
|
+
const pending = runs.filter((r) => r.status === "in_progress" || r.status === "queued" || r.status === "waiting");
|
|
898
|
+
if (pending.length > 0) {
|
|
899
|
+
const names = pending.map((r) => `"${r.name}"`).join(", ");
|
|
900
|
+
return deny(`CI checks are still running on branch "${branch}": ${names}. Wait for all checks to complete and verify they pass.`);
|
|
901
|
+
}
|
|
902
|
+
return allow(`All CI checks passed on branch "${branch}".`);
|
|
903
|
+
} catch {
|
|
904
|
+
return allow("Could not check CI status, skipping.");
|
|
905
|
+
}
|
|
906
|
+
}
|
|
770
907
|
function registerBuiltinPolicies(enabledNames) {
|
|
771
908
|
const enabledSet = new Set(enabledNames);
|
|
772
909
|
for (const policy of BUILTIN_POLICIES) {
|
|
@@ -1102,6 +1239,49 @@ var init_builtin_policies = __esm(() => {
|
|
|
1102
1239
|
match: { events: ["PreToolUse"] },
|
|
1103
1240
|
defaultEnabled: false,
|
|
1104
1241
|
category: "AI Behavior"
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
name: "require-commit-before-stop",
|
|
1245
|
+
description: "Require all changes to be committed before Claude stops",
|
|
1246
|
+
fn: requireCommitBeforeStop,
|
|
1247
|
+
match: { events: ["Stop"] },
|
|
1248
|
+
defaultEnabled: false,
|
|
1249
|
+
category: "Workflow",
|
|
1250
|
+
beta: true
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
name: "require-push-before-stop",
|
|
1254
|
+
description: "Require all commits to be pushed to remote before Claude stops",
|
|
1255
|
+
fn: requirePushBeforeStop,
|
|
1256
|
+
match: { events: ["Stop"] },
|
|
1257
|
+
defaultEnabled: false,
|
|
1258
|
+
category: "Workflow",
|
|
1259
|
+
beta: true,
|
|
1260
|
+
params: {
|
|
1261
|
+
remote: {
|
|
1262
|
+
type: "string",
|
|
1263
|
+
description: "Remote name to push to (default: origin)",
|
|
1264
|
+
default: "origin"
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
name: "require-pr-before-stop",
|
|
1270
|
+
description: "Require a pull request to exist for the current branch before Claude stops",
|
|
1271
|
+
fn: requirePrBeforeStop,
|
|
1272
|
+
match: { events: ["Stop"] },
|
|
1273
|
+
defaultEnabled: false,
|
|
1274
|
+
category: "Workflow",
|
|
1275
|
+
beta: true
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
name: "require-ci-green-before-stop",
|
|
1279
|
+
description: "Require CI checks to pass on the current branch before Claude stops",
|
|
1280
|
+
fn: requireCiGreenBeforeStop,
|
|
1281
|
+
match: { events: ["Stop"] },
|
|
1282
|
+
defaultEnabled: false,
|
|
1283
|
+
category: "Workflow",
|
|
1284
|
+
beta: true
|
|
1105
1285
|
}
|
|
1106
1286
|
];
|
|
1107
1287
|
});
|
|
@@ -1124,6 +1304,7 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1124
1304
|
};
|
|
1125
1305
|
let instructPolicyName = null;
|
|
1126
1306
|
let instructReason = null;
|
|
1307
|
+
const allowMessages = [];
|
|
1127
1308
|
for (const policy of policies) {
|
|
1128
1309
|
const schema = POLICY_PARAMS_MAP.get(policy.name);
|
|
1129
1310
|
let ctx;
|
|
@@ -1195,6 +1376,9 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1195
1376
|
instructReason = result.reason ?? `Instruction from policy: ${policy.name}`;
|
|
1196
1377
|
hookLogInfo(`instruct by "${policy.name}": ${instructReason}`);
|
|
1197
1378
|
}
|
|
1379
|
+
if (result.decision === "allow" && result.reason) {
|
|
1380
|
+
allowMessages.push(result.reason);
|
|
1381
|
+
}
|
|
1198
1382
|
}
|
|
1199
1383
|
if (instructPolicyName && instructReason) {
|
|
1200
1384
|
if (eventType === "Stop") {
|
|
@@ -1222,6 +1406,17 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1222
1406
|
decision: "instruct"
|
|
1223
1407
|
};
|
|
1224
1408
|
}
|
|
1409
|
+
if (allowMessages.length > 0) {
|
|
1410
|
+
const combined = allowMessages.join(`
|
|
1411
|
+
`);
|
|
1412
|
+
const response = {
|
|
1413
|
+
hookSpecificOutput: {
|
|
1414
|
+
hookEventName: eventType,
|
|
1415
|
+
additionalContext: combined
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
return { exitCode: 0, stdout: JSON.stringify(response), stderr: "", policyName: null, reason: combined, decision: "allow" };
|
|
1419
|
+
}
|
|
1225
1420
|
return { exitCode: 0, stdout: "", stderr: "", policyName: null, reason: null, decision: "allow" };
|
|
1226
1421
|
}
|
|
1227
1422
|
var POLICY_PARAMS_MAP;
|
|
@@ -1536,7 +1731,7 @@ var init_hook_activity_store = __esm(() => {
|
|
|
1536
1731
|
});
|
|
1537
1732
|
|
|
1538
1733
|
// package.json
|
|
1539
|
-
var version2 = "0.0.2-beta.
|
|
1734
|
+
var version2 = "0.0.2-beta.3";
|
|
1540
1735
|
var init_package = () => {};
|
|
1541
1736
|
|
|
1542
1737
|
// src/posthog-key.ts
|
|
@@ -2077,6 +2272,19 @@ var init_install_prompt = __esm(() => {
|
|
|
2077
2272
|
init_builtin_policies();
|
|
2078
2273
|
});
|
|
2079
2274
|
|
|
2275
|
+
// src/cli-error.ts
|
|
2276
|
+
var CliError;
|
|
2277
|
+
var init_cli_error = __esm(() => {
|
|
2278
|
+
CliError = class CliError extends Error {
|
|
2279
|
+
exitCode;
|
|
2280
|
+
constructor(message, exitCode = 1) {
|
|
2281
|
+
super(message);
|
|
2282
|
+
this.name = "CliError";
|
|
2283
|
+
this.exitCode = exitCode;
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
});
|
|
2287
|
+
|
|
2080
2288
|
// src/hooks/manager.ts
|
|
2081
2289
|
var exports_manager = {};
|
|
2082
2290
|
__export(exports_manager, {
|
|
@@ -2130,7 +2338,7 @@ function resolveFailproofaiBinary() {
|
|
|
2130
2338
|
return result.split(`
|
|
2131
2339
|
`)[0].trim();
|
|
2132
2340
|
} catch {
|
|
2133
|
-
throw new
|
|
2341
|
+
throw new CliError(`failproofai binary not found in PATH.
|
|
2134
2342
|
` + "Install it globally first: npm install -g failproofai");
|
|
2135
2343
|
}
|
|
2136
2344
|
}
|
|
@@ -2144,7 +2352,7 @@ function validatePolicyNames(names) {
|
|
|
2144
2352
|
const invalid = names.filter((n) => !VALID_POLICY_NAMES.has(n));
|
|
2145
2353
|
if (invalid.length > 0) {
|
|
2146
2354
|
const validList = [...VALID_POLICY_NAMES].join(", ");
|
|
2147
|
-
throw new
|
|
2355
|
+
throw new CliError(`Unknown policy name(s): ${invalid.join(", ")}
|
|
2148
2356
|
` + `Valid policies: ${validList}`);
|
|
2149
2357
|
}
|
|
2150
2358
|
}
|
|
@@ -2211,6 +2419,15 @@ function removeHooksFromSettingsFile(settingsPath) {
|
|
|
2211
2419
|
return removed;
|
|
2212
2420
|
}
|
|
2213
2421
|
async function installHooks(policyNames, scope = "user", cwd, includeBeta = false, source, customPoliciesPath, removeCustomHooks = false) {
|
|
2422
|
+
if (policyNames !== undefined && policyNames.length > 0) {
|
|
2423
|
+
const nonAllNames = policyNames.filter((n) => n !== "all");
|
|
2424
|
+
if (nonAllNames.length > 0)
|
|
2425
|
+
validatePolicyNames(nonAllNames);
|
|
2426
|
+
if (policyNames.includes("all") && nonAllNames.length > 0) {
|
|
2427
|
+
throw new CliError(`"all" cannot be combined with specific policy names.
|
|
2428
|
+
` + `Use either: --install all or --install block-sudo sanitize-jwt ...`);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2214
2431
|
const binaryPath = resolveFailproofaiBinary();
|
|
2215
2432
|
const previousConfig = readHooksConfig();
|
|
2216
2433
|
const previousEnabled = new Set(previousConfig.enabledPolicies);
|
|
@@ -2220,8 +2437,6 @@ async function installHooks(policyNames, scope = "user", cwd, includeBeta = fals
|
|
|
2220
2437
|
if (policyNames.length === 1 && policyNames[0] === "all") {
|
|
2221
2438
|
incoming = BUILTIN_POLICIES.filter((p) => includeBeta || !p.beta).map((p) => p.name);
|
|
2222
2439
|
} else {
|
|
2223
|
-
if (policyNames.length > 0)
|
|
2224
|
-
validatePolicyNames(policyNames);
|
|
2225
2440
|
incoming = policyNames;
|
|
2226
2441
|
}
|
|
2227
2442
|
selectedPolicies = [...new Set([...previousConfig.enabledPolicies, ...incoming])];
|
|
@@ -2565,6 +2780,7 @@ var init_manager = __esm(() => {
|
|
|
2565
2780
|
init_custom_hooks_loader();
|
|
2566
2781
|
init_hook_telemetry();
|
|
2567
2782
|
init_telemetry_id();
|
|
2783
|
+
init_cli_error();
|
|
2568
2784
|
VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
|
|
2569
2785
|
});
|
|
2570
2786
|
|
|
@@ -2719,12 +2935,29 @@ var init_launch = __esm(() => {
|
|
|
2719
2935
|
init_package();
|
|
2720
2936
|
});
|
|
2721
2937
|
|
|
2938
|
+
// src/cli-error.ts
|
|
2939
|
+
var exports_cli_error = {};
|
|
2940
|
+
__export(exports_cli_error, {
|
|
2941
|
+
CliError: () => CliError2
|
|
2942
|
+
});
|
|
2943
|
+
var CliError2;
|
|
2944
|
+
var init_cli_error2 = __esm(() => {
|
|
2945
|
+
CliError2 = class CliError2 extends Error {
|
|
2946
|
+
exitCode;
|
|
2947
|
+
constructor(message, exitCode = 1) {
|
|
2948
|
+
super(message);
|
|
2949
|
+
this.name = "CliError";
|
|
2950
|
+
this.exitCode = exitCode;
|
|
2951
|
+
}
|
|
2952
|
+
};
|
|
2953
|
+
});
|
|
2954
|
+
|
|
2722
2955
|
// bin/failproofai.mjs
|
|
2723
2956
|
import { realpathSync as realpathSync2 } from "fs";
|
|
2724
2957
|
import { dirname as dirname5, resolve as resolve8 } from "path";
|
|
2725
2958
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2726
2959
|
// package.json
|
|
2727
|
-
var version = "0.0.2-beta.
|
|
2960
|
+
var version = "0.0.2-beta.3";
|
|
2728
2961
|
|
|
2729
2962
|
// bin/failproofai.mjs
|
|
2730
2963
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
@@ -2736,9 +2969,32 @@ if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
|
2736
2969
|
var args = process.argv.slice(2);
|
|
2737
2970
|
if (args[0] === "p")
|
|
2738
2971
|
args[0] = "policies";
|
|
2739
|
-
var
|
|
2740
|
-
if (
|
|
2741
|
-
|
|
2972
|
+
var hookIdx = args.indexOf("--hook");
|
|
2973
|
+
if (hookIdx >= 0) {
|
|
2974
|
+
if (!args[hookIdx + 1]) {
|
|
2975
|
+
console.error("Error: Missing event type after --hook");
|
|
2976
|
+
console.error("Usage: failproofai --hook <event> (e.g. PreToolUse, PostToolUse)");
|
|
2977
|
+
process.exit(1);
|
|
2978
|
+
}
|
|
2979
|
+
try {
|
|
2980
|
+
const { handleHookEvent: handleHookEvent2 } = await Promise.resolve().then(() => (init_handler(), exports_handler));
|
|
2981
|
+
const exitCode = await handleHookEvent2(args[hookIdx + 1]);
|
|
2982
|
+
process.exit(exitCode);
|
|
2983
|
+
} catch (err) {
|
|
2984
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2985
|
+
console.error(`Unexpected error: ${msg}`);
|
|
2986
|
+
process.exit(2);
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
async function runCli() {
|
|
2990
|
+
const SUBCOMMANDS = ["policies"];
|
|
2991
|
+
if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
|
|
2992
|
+
const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
|
|
2993
|
+
if (extraArgs.length > 0) {
|
|
2994
|
+
throw new CliError3(`Unexpected argument: ${extraArgs[0]}
|
|
2995
|
+
Run \`failproofai --help\` for usage.`);
|
|
2996
|
+
}
|
|
2997
|
+
console.log(`
|
|
2742
2998
|
failproofai v${version}
|
|
2743
2999
|
|
|
2744
3000
|
USAGE
|
|
@@ -2778,25 +3034,24 @@ LINKS
|
|
|
2778
3034
|
\u2B50 Star us: https://github.com/exospherehost/failproofai
|
|
2779
3035
|
\uD83D\uDCD6 Docs: https://befailproof.ai
|
|
2780
3036
|
`.trimStart());
|
|
2781
|
-
|
|
2782
|
-
}
|
|
2783
|
-
if (args.includes("--version") || args.includes("-v")) {
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
console.log(`
|
|
3037
|
+
process.exit(0);
|
|
3038
|
+
}
|
|
3039
|
+
if ((args.includes("--version") || args.includes("-v")) && !SUBCOMMANDS.includes(args[0])) {
|
|
3040
|
+
const extraArgs = args.filter((a) => a !== "--version" && a !== "-v");
|
|
3041
|
+
if (extraArgs.length > 0) {
|
|
3042
|
+
throw new CliError3(`Unexpected argument: ${extraArgs[0]}
|
|
3043
|
+
Run \`failproofai --help\` for usage.`);
|
|
3044
|
+
}
|
|
3045
|
+
console.log(version);
|
|
3046
|
+
process.exit(0);
|
|
3047
|
+
}
|
|
3048
|
+
if (args[0] === "policies") {
|
|
3049
|
+
const subArgs = args.slice(1);
|
|
3050
|
+
const isInstall = subArgs.includes("--install") || subArgs.includes("-i");
|
|
3051
|
+
const isUninstall = subArgs.includes("--uninstall") || subArgs.includes("-u");
|
|
3052
|
+
const isHelp = subArgs.includes("--help") || subArgs.includes("-h");
|
|
3053
|
+
if (isHelp) {
|
|
3054
|
+
console.log(`
|
|
2800
3055
|
failproofai policies \u2014 manage Failproof AI policies
|
|
2801
3056
|
|
|
2802
3057
|
USAGE
|
|
@@ -2827,65 +3082,119 @@ EXAMPLES
|
|
|
2827
3082
|
failproofai policies -u
|
|
2828
3083
|
failproofai policies --uninstall --custom
|
|
2829
3084
|
`.trimStart());
|
|
3085
|
+
process.exit(0);
|
|
3086
|
+
}
|
|
3087
|
+
if (isInstall) {
|
|
3088
|
+
const { installHooks: installHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
3089
|
+
const scopeIdx = subArgs.indexOf("--scope");
|
|
3090
|
+
const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
|
|
3091
|
+
if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
|
|
3092
|
+
throw new CliError3("Missing value for --scope. Valid values: user, project, local");
|
|
3093
|
+
}
|
|
3094
|
+
if (scopeIdx >= 0 && !["user", "project", "local"].includes(scope)) {
|
|
3095
|
+
throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local`);
|
|
3096
|
+
}
|
|
3097
|
+
const customIdx = subArgs.includes("--custom") ? subArgs.indexOf("--custom") : subArgs.includes("-c") ? subArgs.indexOf("-c") : -1;
|
|
3098
|
+
const customPoliciesPath = customIdx >= 0 ? subArgs[customIdx + 1] : undefined;
|
|
3099
|
+
if (customIdx >= 0 && (!customPoliciesPath || customPoliciesPath.startsWith("-"))) {
|
|
3100
|
+
throw new CliError3(`Missing path after --custom/-c
|
|
3101
|
+
Usage: --custom <path> (e.g. --custom ./my-policies.js)`);
|
|
3102
|
+
}
|
|
3103
|
+
const includeBeta = subArgs.includes("--beta");
|
|
3104
|
+
const consumedIdxs = new Set;
|
|
3105
|
+
if (scopeIdx >= 0)
|
|
3106
|
+
consumedIdxs.add(scopeIdx + 1);
|
|
3107
|
+
if (customIdx >= 0)
|
|
3108
|
+
consumedIdxs.add(customIdx + 1);
|
|
3109
|
+
const flags = new Set(["--install", "-i", "--scope", "--beta", "--custom", "-c"]);
|
|
3110
|
+
const unknownInstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
|
|
3111
|
+
if (unknownInstallFlag) {
|
|
3112
|
+
throw new CliError3(`Unknown flag: ${unknownInstallFlag}
|
|
3113
|
+
Run \`failproofai policies --help\` for usage.`);
|
|
3114
|
+
}
|
|
3115
|
+
const explicitPolicyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
|
|
3116
|
+
const policyNames = explicitPolicyNames.length > 0 ? explicitPolicyNames : customPoliciesPath !== undefined ? [] : undefined;
|
|
3117
|
+
await installHooks2(policyNames, scope, undefined, includeBeta, undefined, customPoliciesPath);
|
|
3118
|
+
process.exit(0);
|
|
3119
|
+
}
|
|
3120
|
+
if (isUninstall) {
|
|
3121
|
+
const { removeHooks: removeHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
3122
|
+
const scopeIdx = subArgs.indexOf("--scope");
|
|
3123
|
+
const scope = scopeIdx >= 0 ? subArgs[scopeIdx + 1] : "user";
|
|
3124
|
+
if (scopeIdx >= 0 && (!scope || scope.startsWith("-"))) {
|
|
3125
|
+
throw new CliError3("Missing value for --scope. Valid values: user, project, local, all");
|
|
3126
|
+
}
|
|
3127
|
+
if (scopeIdx >= 0 && !["user", "project", "local", "all"].includes(scope)) {
|
|
3128
|
+
throw new CliError3(`Invalid scope: ${scope}. Valid values: user, project, local, all`);
|
|
3129
|
+
}
|
|
3130
|
+
const betaOnly = subArgs.includes("--beta");
|
|
3131
|
+
const removeCustomHooks = subArgs.includes("--custom") || subArgs.includes("-c");
|
|
3132
|
+
const consumedIdxs = new Set;
|
|
3133
|
+
if (scopeIdx >= 0)
|
|
3134
|
+
consumedIdxs.add(scopeIdx + 1);
|
|
3135
|
+
const flags = new Set(["--uninstall", "-u", "--scope", "--beta", "--custom", "-c"]);
|
|
3136
|
+
const unknownUninstallFlag = subArgs.find((a) => a.startsWith("-") && !flags.has(a));
|
|
3137
|
+
if (unknownUninstallFlag) {
|
|
3138
|
+
throw new CliError3(`Unknown flag: ${unknownUninstallFlag}
|
|
3139
|
+
Run \`failproofai policies --help\` for usage.`);
|
|
3140
|
+
}
|
|
3141
|
+
const policyNames = subArgs.filter((a, idx) => !a.startsWith("-") && !consumedIdxs.has(idx));
|
|
3142
|
+
await removeHooks2(policyNames.length > 0 ? policyNames : undefined, scope, undefined, { betaOnly, removeCustomHooks });
|
|
3143
|
+
process.exit(0);
|
|
3144
|
+
}
|
|
3145
|
+
const knownListFlags = new Set(["--install", "-i", "--uninstall", "-u", "--help", "-h", "--list"]);
|
|
3146
|
+
const unknownListArg = subArgs.find((a) => a.startsWith("-") && !knownListFlags.has(a));
|
|
3147
|
+
if (unknownListArg) {
|
|
3148
|
+
throw new CliError3(`Unknown flag: ${unknownListArg}
|
|
3149
|
+
Run \`failproofai policies --help\` for usage.`);
|
|
3150
|
+
}
|
|
3151
|
+
const positionalArgs = subArgs.filter((a) => !a.startsWith("-"));
|
|
3152
|
+
if (positionalArgs.length > 0) {
|
|
3153
|
+
throw new CliError3(`Unexpected argument: ${positionalArgs[0]}
|
|
3154
|
+
Run \`failproofai policies --help\` for usage.`);
|
|
3155
|
+
}
|
|
3156
|
+
const { listHooks: listHooks2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
3157
|
+
await listHooks2();
|
|
2830
3158
|
process.exit(0);
|
|
2831
3159
|
}
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
}
|
|
2858
|
-
const {
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
}
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
const primary = ["--version", "--help", "--hook", "policies"];
|
|
2874
|
-
const closest = primary.reduce((best, flag) => {
|
|
2875
|
-
const dist = levenshtein(unknownFlag, flag);
|
|
2876
|
-
return dist < best.dist ? { flag, dist } : best;
|
|
2877
|
-
}, { flag: primary[0], dist: Infinity });
|
|
2878
|
-
console.error(`Unknown flag: ${unknownFlag}`);
|
|
2879
|
-
console.error(`Did you mean: ${closest.flag}?`);
|
|
2880
|
-
console.error(`Run \`failproofai --help\` for usage details.`);
|
|
2881
|
-
process.exit(1);
|
|
2882
|
-
}
|
|
2883
|
-
var unknownSubcommand = args.find((a) => !a.startsWith("-") && a !== "policies");
|
|
2884
|
-
if (unknownSubcommand) {
|
|
2885
|
-
console.error(`Unknown command: ${unknownSubcommand}`);
|
|
2886
|
-
console.error(`Did you mean: failproofai policies?`);
|
|
2887
|
-
console.error(`Run \`failproofai --help\` for usage details.`);
|
|
2888
|
-
process.exit(1);
|
|
2889
|
-
}
|
|
2890
|
-
var { launch: launch2 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
|
|
2891
|
-
launch2("start");
|
|
3160
|
+
const knownFlags = ["--version", "-v", "--help", "-h", "--hook"];
|
|
3161
|
+
const unknownFlag = args.find((a) => a.startsWith("-") && !knownFlags.includes(a));
|
|
3162
|
+
if (unknownFlag) {
|
|
3163
|
+
let levenshtein = function(a, b) {
|
|
3164
|
+
const m = a.length, n = b.length;
|
|
3165
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
3166
|
+
for (let i = 1;i <= m; i++)
|
|
3167
|
+
for (let j = 1;j <= n; j++)
|
|
3168
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
3169
|
+
return dp[m][n];
|
|
3170
|
+
};
|
|
3171
|
+
const primary = ["--version", "--help", "--hook", "policies"];
|
|
3172
|
+
const closest = primary.reduce((best, flag) => {
|
|
3173
|
+
const dist = levenshtein(unknownFlag, flag);
|
|
3174
|
+
return dist < best.dist ? { flag, dist } : best;
|
|
3175
|
+
}, { flag: primary[0], dist: Infinity });
|
|
3176
|
+
throw new CliError3(`Unknown flag: ${unknownFlag}
|
|
3177
|
+
Did you mean: ${closest.flag}?
|
|
3178
|
+
Run \`failproofai --help\` for usage details.`);
|
|
3179
|
+
}
|
|
3180
|
+
const unknownSubcommand = args.find((a) => !a.startsWith("-") && a !== "policies");
|
|
3181
|
+
if (unknownSubcommand) {
|
|
3182
|
+
throw new CliError3(`Unknown command: ${unknownSubcommand}
|
|
3183
|
+
Did you mean: failproofai policies?
|
|
3184
|
+
Run \`failproofai --help\` for usage details.`);
|
|
3185
|
+
}
|
|
3186
|
+
const { launch: launch2 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
|
|
3187
|
+
launch2("start");
|
|
3188
|
+
}
|
|
3189
|
+
var { CliError: CliError3 } = await Promise.resolve().then(() => (init_cli_error2(), exports_cli_error));
|
|
3190
|
+
try {
|
|
3191
|
+
await runCli();
|
|
3192
|
+
} catch (err) {
|
|
3193
|
+
if (err instanceof CliError3) {
|
|
3194
|
+
console.error(`Error: ${err.message}`);
|
|
3195
|
+
process.exit(err.exitCode);
|
|
3196
|
+
}
|
|
3197
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3198
|
+
console.error(`Unexpected error: ${msg}`);
|
|
3199
|
+
process.exit(2);
|
|
3200
|
+
}
|
|
@@ -69,8 +69,8 @@ function clearCustomHooks() {
|
|
|
69
69
|
g[REGISTRY_KEY] = [];
|
|
70
70
|
}
|
|
71
71
|
// src/hooks/policy-helpers.ts
|
|
72
|
-
function allow() {
|
|
73
|
-
return { decision: "allow" };
|
|
72
|
+
function allow(reason) {
|
|
73
|
+
return reason ? { decision: "allow", reason } : { decision: "allow" };
|
|
74
74
|
}
|
|
75
75
|
function deny(reason) {
|
|
76
76
|
return { decision: "deny", reason };
|