Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 0.0.11-beta.1 — 2026-05-20

### Breaking
- Default policy namespace renamed from `exospherehost` to `failproofai`. Configs that explicitly reference builtins as `exospherehost/<name>` must update to `failproofai/<name>`. Flat-name shorthand (e.g. `"sanitize-jwt"`) continues to work unchanged because it auto-resolves to the new default namespace. Builtin docs (EN + 14 translations) updated to show the new namespace.

### Docs
- Rename GitHub org URLs across `package.json` metadata, README CI badge (EN + 14 translated READMEs), CONTRIBUTING, in-app "Star us" banners (`bin/failproofai.mjs`, `scripts/launch.ts`, navbar, reach-developers component), Mintlify `docs/docs.json`, and 30 translated docs (`package-aliases.mdx` issues link + `examples.mdx` repo-tree link) to reflect the `exospherehost` → `failproofai` org rename. X social handle in `docs/docs.json` updated from `x.com/exospherehost` to `x.com/failproofai`.

### Fixes
- Remove orphan `exospheresmall` token from the Next.js proxy matcher in `proxy.ts` — no asset by that name exists in the repo.

## 0.0.11-beta.0 — 2026-05-13

### Docs
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Thanks for your interest in contributing! Here's how to get started.
## Development Setup

```bash
git clone https://github.com/exosphereHost/failproofai.git
git clone https://github.com/failproofai/failproofai.git
cd failproofai
bun install
bun run dev
Expand Down Expand Up @@ -73,4 +73,4 @@ failproofai/

## Reporting Issues

Found a bug or have a feature idea? [Open an issue](https://github.com/exosphereHost/failproofai/issues). The issue templates will guide you through providing the right details.
Found a bug or have a feature idea? [Open an issue](https://github.com/failproofai/failproofai/issues). The issue templates will guide you through providing the right details.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<img src="https://d2wq11aau0arks.cloudfront.net/failproof/logo-wordmark.png" alt="failproof ai" width="220" />

[![npm](https://img.shields.io/npm/v/failproofai?style=flat-square&color=CB3837)](https://www.npmjs.com/package/failproofai)
[![CI](https://img.shields.io/github/actions/workflow/status/exospherehost/failproofai/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/exospherehost/failproofai/actions)
[![CI](https://img.shields.io/github/actions/workflow/status/failproofai/failproofai/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/failproofai/failproofai/actions)
[![Slack](https://img.shields.io/badge/Slack-join%20us-4A154B?style=flat-square&logo=slack)](https://join.slack.com/t/failproofai/shared_invite/zt-3v63b7k5e-O3NBHmj8X6n9gZSGDx6ggQ)
[![Docs](https://img.shields.io/badge/docs-befailproof.ai-002CA7?style=flat-square)](https://docs.befailproof.ai)
[![License](https://img.shields.io/badge/license-MIT%20%2B%20Commons%20Clause-blue?style=flat-square)](./LICENSE)
Expand Down
4 changes: 2 additions & 2 deletions __tests__/components/reach-developers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe("ReachDevelopers", () => {
const featureLink = screen.getByText("Request a Feature").closest("a");
expect(featureLink).toHaveAttribute(
"href",
expect.stringContaining("github.com/exospherehost/failproofai")
expect.stringContaining("github.com/failproofai/failproofai")
);
expect(featureLink).toHaveAttribute(
"href",
Expand All @@ -48,7 +48,7 @@ describe("ReachDevelopers", () => {
const bugLink = screen.getByText("Report an Issue").closest("a");
expect(bugLink).toHaveAttribute(
"href",
expect.stringContaining("github.com/exospherehost/failproofai")
expect.stringContaining("github.com/failproofai/failproofai")
);
});

Expand Down
12 changes: 6 additions & 6 deletions __tests__/hooks/builtin-policies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,23 @@ describe("hooks/builtin-policies", () => {
const policies = getPoliciesForEvent("PreToolUse", "Bash");
expect(policies).toHaveLength(2);
expect(policies.map((p) => p.name).sort()).toEqual([
"exospherehost/block-rm-rf",
"exospherehost/block-sudo",
"failproofai/block-rm-rf",
"failproofai/block-sudo",
]);
});

it("accepts qualified names in enabledPolicies (forward compat)", () => {
registerBuiltinPolicies(["exospherehost/block-sudo", "exospherehost/block-rm-rf"]);
registerBuiltinPolicies(["failproofai/block-sudo", "failproofai/block-rm-rf"]);
const policies = getPoliciesForEvent("PreToolUse", "Bash");
expect(policies).toHaveLength(2);
expect(policies.map((p) => p.name).sort()).toEqual([
"exospherehost/block-rm-rf",
"exospherehost/block-sudo",
"failproofai/block-rm-rf",
"failproofai/block-sudo",
]);
});

it("treats flat and qualified names as equivalent (mixed config works)", () => {
registerBuiltinPolicies(["block-sudo", "exospherehost/block-rm-rf"]);
registerBuiltinPolicies(["block-sudo", "failproofai/block-rm-rf"]);
const policies = getPoliciesForEvent("PreToolUse", "Bash");
expect(policies).toHaveLength(2);
});
Expand Down
54 changes: 27 additions & 27 deletions __tests__/hooks/policy-evaluator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe("hooks/policy-evaluator", () => {
"Blocked Bash by failproofai because: blocked, as per the policy configured by the user",
);
expect(parsed.hookSpecificOutput.hookEventName).toBe("PreToolUse");
expect(result.policyName).toBe("exospherehost/blocker");
expect(result.policyName).toBe("failproofai/blocker");
expect(result.reason).toBe("blocked");
});

Expand All @@ -60,7 +60,7 @@ describe("hooks/policy-evaluator", () => {
expect(parsed.hookSpecificOutput.additionalContext).toBe(
"Blocked Read by failproofai because: JWT found, as per the policy configured by the user",
);
expect(result.policyName).toBe("exospherehost/jwt-scrub");
expect(result.policyName).toBe("failproofai/jwt-scrub");
expect(result.reason).toBe("JWT found");
});

Expand Down Expand Up @@ -114,8 +114,8 @@ describe("hooks/policy-evaluator", () => {
expect(result.decision).toBe("instruct");
const parsed = JSON.parse(result.stdout);
expect(parsed.hookSpecificOutput.additionalContext).toContain("You should try something else");
expect(result.policyName).toBe("exospherehost/advisor");
expect(result.policyNames).toEqual(["exospherehost/advisor"]);
expect(result.policyName).toBe("failproofai/advisor");
expect(result.policyNames).toEqual(["failproofai/advisor"]);
expect(result.reason).toBe("You should try something else");
});

Expand All @@ -131,7 +131,7 @@ describe("hooks/policy-evaluator", () => {

const result = await evaluatePolicies("PreToolUse", { tool_name: "Bash" });
expect(result.decision).toBe("deny");
expect(result.policyName).toBe("exospherehost/blocker");
expect(result.policyName).toBe("failproofai/blocker");
const parsed = JSON.parse(result.stdout);
expect(parsed.hookSpecificOutput.permissionDecision).toBe("deny");
});
Expand Down Expand Up @@ -172,7 +172,7 @@ describe("hooks/policy-evaluator", () => {
expect(result.stdout).toBe("");
expect(result.stderr).toContain("MANDATORY ACTION REQUIRED");
expect(result.stderr).toContain("Unsatisfied intents remain");
expect(result.policyName).toBe("exospherehost/verify");
expect(result.policyName).toBe("failproofai/verify");
});

it("SubagentStop + instruct also returns exitCode 2 (Claude path mirrors Stop)", async () => {
Expand Down Expand Up @@ -310,7 +310,7 @@ describe("hooks/policy-evaluator", () => {
const parsed = JSON.parse(result.stdout) as { permission?: string; reason?: string };
expect(parsed.permission).toBe("deny");
expect(parsed.reason).toContain("MANDATORY ACTION REQUIRED");
expect(parsed.reason).toContain("policy: exospherehost/verify");
expect(parsed.reason).toContain("policy: failproofai/verify");
expect(parsed.reason).toContain("needs verification");
});

Expand Down Expand Up @@ -369,8 +369,8 @@ describe("hooks/policy-evaluator", () => {

const result = await evaluatePolicies("PreToolUse", { tool_name: "Read" });
expect(result.decision).toBe("instruct");
expect(result.policyName).toBe("exospherehost/first");
expect(result.policyNames).toEqual(["exospherehost/first", "exospherehost/second"]);
expect(result.policyName).toBe("failproofai/first");
expect(result.policyNames).toEqual(["failproofai/first", "failproofai/second"]);
expect(result.reason).toBe("first warning\nsecond warning");
const parsed = JSON.parse(result.stdout);
expect(parsed.hookSpecificOutput.additionalContext).toContain("first warning");
Expand All @@ -388,11 +388,11 @@ describe("hooks/policy-evaluator", () => {
expect(result.exitCode).toBe(0);
expect(result.decision).toBe("allow");
expect(result.reason).toBe("All checks passed");
expect(result.policyName).toBe("exospherehost/info");
expect(result.policyNames).toEqual(["exospherehost/info"]);
expect(result.policyName).toBe("failproofai/info");
expect(result.policyNames).toEqual(["failproofai/info"]);
const parsed = JSON.parse(result.stdout);
expect(parsed.hookSpecificOutput.additionalContext).toBe("Note from failproofai: All checks passed");
expect(result.stderr).toContain("[failproofai] exospherehost/info: All checks passed");
expect(result.stderr).toContain("[failproofai] failproofai/info: All checks passed");
});

it("combines multiple allow messages with newline", async () => {
Expand All @@ -408,12 +408,12 @@ describe("hooks/policy-evaluator", () => {
const result = await evaluatePolicies("Stop", {});
expect(result.exitCode).toBe(0);
expect(result.decision).toBe("allow");
expect(result.policyName).toBe("exospherehost/info1");
expect(result.policyNames).toEqual(["exospherehost/info1", "exospherehost/info2"]);
expect(result.policyName).toBe("failproofai/info1");
expect(result.policyNames).toEqual(["failproofai/info1", "failproofai/info2"]);
const parsed = JSON.parse(result.stdout);
expect(parsed.reason).toBe("Commit check passed\nPush check passed");
expect(result.stderr).toContain("[failproofai] exospherehost/info1: Commit check passed");
expect(result.stderr).toContain("[failproofai] exospherehost/info2: Push check passed");
expect(result.stderr).toContain("[failproofai] failproofai/info1: Commit check passed");
expect(result.stderr).toContain("[failproofai] failproofai/info2: Push check passed");
});

it("returns empty stdout when allow has no reason (backward-compatible)", async () => {
Expand All @@ -440,7 +440,7 @@ describe("hooks/policy-evaluator", () => {

const result = await evaluatePolicies("PreToolUse", { tool_name: "Bash" });
expect(result.decision).toBe("deny");
expect(result.policyName).toBe("exospherehost/blocker");
expect(result.policyName).toBe("failproofai/blocker");
});

it("instruct takes precedence over allow with message", async () => {
Expand All @@ -455,7 +455,7 @@ describe("hooks/policy-evaluator", () => {

const result = await evaluatePolicies("PreToolUse", { tool_name: "Bash" });
expect(result.decision).toBe("instruct");
expect(result.policyName).toBe("exospherehost/advisor");
expect(result.policyName).toBe("failproofai/advisor");
});
});

Expand Down Expand Up @@ -512,7 +512,7 @@ describe("hooks/policy-evaluator", () => {
it("flat policyParams key only matches default-namespace policies (not custom/myorg)", async () => {
// A custom hook registered with a third-party namespace must NOT pick up
// params keyed by the bare short name — that key belongs to the
// exospherehost/<short> builtin slot, not myorg/<short>.
// failproofai/<short> builtin slot, not myorg/<short>.
let capturedHint: unknown = null;
registerPolicy("myorg/foo", "desc", () => ({
decision: "deny",
Expand Down Expand Up @@ -804,10 +804,10 @@ describe("hooks/policy-evaluator", () => {
const parsed = JSON.parse(result.stdout) as { permission?: string; reason?: string };
expect(parsed.permission).toBe("deny");
expect(parsed.reason).toContain("MANDATORY ACTION REQUIRED");
expect(parsed.reason).toContain("policy: exospherehost/stop-blocker");
expect(parsed.reason).toContain("policy: failproofai/stop-blocker");
expect(parsed.reason).toContain("tests not run");
expect(result.decision).toBe("deny");
expect(result.policyName).toBe("exospherehost/stop-blocker");
expect(result.policyName).toBe("failproofai/stop-blocker");
expect(result.reason).toBe("tests not run");
});

Expand Down Expand Up @@ -876,7 +876,7 @@ describe("hooks/policy-evaluator", () => {

const result = await evaluatePolicies("Stop", {});
expect(result.decision).toBe("deny");
expect(result.policyName).toBe("exospherehost/require-commit");
expect(result.policyName).toBe("failproofai/require-commit");
expect(policyCalls).toEqual(["commit"]);
});

Expand All @@ -901,7 +901,7 @@ describe("hooks/policy-evaluator", () => {
const result = await evaluatePolicies("Stop", {});
expect(result.exitCode).toBe(0);
expect(result.decision).toBe("allow");
expect(result.policyNames).toEqual(["exospherehost/wf-commit", "exospherehost/wf-push", "exospherehost/wf-pr", "exospherehost/wf-ci"]);
expect(result.policyNames).toEqual(["failproofai/wf-commit", "failproofai/wf-push", "failproofai/wf-pr", "failproofai/wf-ci"]);
const parsed = JSON.parse(result.stdout);
expect(parsed.reason).toContain("All changes committed");
expect(parsed.reason).toContain("All commits pushed");
Expand All @@ -921,7 +921,7 @@ describe("hooks/policy-evaluator", () => {

const result = await evaluatePolicies("Stop", {});
expect(result.decision).toBe("deny");
expect(result.policyName).toBe("exospherehost/wf-push");
expect(result.policyName).toBe("failproofai/wf-push");
expect(result.reason).toBe("unpushed commits");
});

Expand Down Expand Up @@ -954,8 +954,8 @@ describe("hooks/policy-evaluator", () => {
const result = await evaluatePolicies("Stop", {});
expect(result.exitCode).toBe(0);
expect(result.decision).toBe("allow");
expect(result.policyName).toBe("exospherehost/informative");
expect(result.policyNames).toEqual(["exospherehost/informative"]);
expect(result.policyName).toBe("failproofai/informative");
expect(result.policyNames).toEqual(["failproofai/informative"]);
const parsed = JSON.parse(result.stdout);
expect(parsed.reason).toBe("CI is green");
});
Expand All @@ -971,7 +971,7 @@ describe("hooks/policy-evaluator", () => {

const result = await evaluatePolicies("Stop", {});
expect(result.decision).toBe("deny");
expect(result.policyName).toBe("exospherehost/checker");
expect(result.policyName).toBe("failproofai/checker");
});
});

Expand Down
30 changes: 15 additions & 15 deletions __tests__/hooks/policy-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe("hooks/policy-registry", () => {
registerPolicy("test", "desc", () => ({ decision: "allow" }), { events: ["PreToolUse"] });
const policies = getPoliciesForEvent("PreToolUse");
expect(policies).toHaveLength(1);
expect(policies[0].name).toBe("exospherehost/test");
expect(policies[0].name).toBe("failproofai/test");
});

it("upserts by name", () => {
Expand All @@ -33,9 +33,9 @@ describe("hooks/policy-registry", () => {
registerPolicy("post", "desc", () => ({ decision: "allow" }), { events: ["PostToolUse"] });

expect(getPoliciesForEvent("PreToolUse")).toHaveLength(1);
expect(getPoliciesForEvent("PreToolUse")[0].name).toBe("exospherehost/pre");
expect(getPoliciesForEvent("PreToolUse")[0].name).toBe("failproofai/pre");
expect(getPoliciesForEvent("PostToolUse")).toHaveLength(1);
expect(getPoliciesForEvent("PostToolUse")[0].name).toBe("exospherehost/post");
expect(getPoliciesForEvent("PostToolUse")[0].name).toBe("failproofai/post");
});

it("filters by tool name", () => {
Expand All @@ -52,7 +52,7 @@ describe("hooks/policy-registry", () => {

const readPolicies = getPoliciesForEvent("PreToolUse", "Read");
expect(readPolicies).toHaveLength(1);
expect(readPolicies[0].name).toBe("exospherehost/any-tool");
expect(readPolicies[0].name).toBe("failproofai/any-tool");
});

it("sorts by priority (higher first)", () => {
Expand All @@ -62,9 +62,9 @@ describe("hooks/policy-registry", () => {

const policies = getPoliciesForEvent("PreToolUse");
expect(policies.map((p) => p.name)).toEqual([
"exospherehost/high",
"exospherehost/mid",
"exospherehost/low",
"failproofai/high",
"failproofai/mid",
"failproofai/low",
]);
});

Expand All @@ -90,35 +90,35 @@ describe("hooks/policy-registry", () => {
});

describe("namespace canonicalization", () => {
it("DEFAULT_POLICY_NAMESPACE is exospherehost", () => {
expect(DEFAULT_POLICY_NAMESPACE).toBe("exospherehost");
it("DEFAULT_POLICY_NAMESPACE is failproofai", () => {
expect(DEFAULT_POLICY_NAMESPACE).toBe("failproofai");
});

it("normalizePolicyName prepends default namespace to flat names", () => {
expect(normalizePolicyName("foo")).toBe("exospherehost/foo");
expect(normalizePolicyName("sanitize-jwt")).toBe("exospherehost/sanitize-jwt");
expect(normalizePolicyName("foo")).toBe("failproofai/foo");
expect(normalizePolicyName("sanitize-jwt")).toBe("failproofai/sanitize-jwt");
});

it("normalizePolicyName leaves already-namespaced names untouched", () => {
expect(normalizePolicyName("exospherehost/foo")).toBe("exospherehost/foo");
expect(normalizePolicyName("failproofai/foo")).toBe("failproofai/foo");
expect(normalizePolicyName("myorg/bar")).toBe("myorg/bar");
expect(normalizePolicyName("custom/hook")).toBe("custom/hook");
});

it("registering a flat name and a qualified name for the same policy upserts (not duplicates)", () => {
registerPolicy("dup", "first", () => ({ decision: "allow" }), { events: ["PreToolUse"] });
registerPolicy("exospherehost/dup", "second", () => ({ decision: "allow" }), { events: ["PreToolUse"] });
registerPolicy("failproofai/dup", "second", () => ({ decision: "allow" }), { events: ["PreToolUse"] });
const policies = getPoliciesForEvent("PreToolUse");
expect(policies).toHaveLength(1);
expect(policies[0].name).toBe("exospherehost/dup");
expect(policies[0].name).toBe("failproofai/dup");
expect(policies[0].description).toBe("second");
});

it("custom-namespace policies coexist with same short-name builtins", () => {
registerPolicy("foo", "builtin", () => ({ decision: "allow" }), { events: ["PreToolUse"] });
registerPolicy("myorg/foo", "custom", () => ({ decision: "allow" }), { events: ["PreToolUse"] });
const policies = getPoliciesForEvent("PreToolUse");
expect(policies.map((p) => p.name).sort()).toEqual(["exospherehost/foo", "myorg/foo"]);
expect(policies.map((p) => p.name).sort()).toEqual(["failproofai/foo", "myorg/foo"]);
});
});
});
2 changes: 1 addition & 1 deletion bin/failproofai.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ EXAMPLES
failproofai policies --uninstall --custom

LINKS
⭐ Star us: https://github.com/exospherehost/failproofai
⭐ Star us: https://github.com/failproofai/failproofai
📖 Docs: https://befailproof.ai
💬 Slack: https://join.slack.com/t/failproofai/shared_invite/zt-3v63b7k5e-O3NBHmj8X6n9gZSGDx6ggQ
`.trimStart());
Expand Down
2 changes: 1 addition & 1 deletion components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const Navbar: React.FC<{ disabledPages?: string[] }> = ({ disabledPages =
<div className="flex items-center justify-between h-16">
<div className="flex items-center gap-3">
<a
href="https://github.com/exospherehost/failproofai"
href="https://github.com/failproofai/failproofai"
target="_blank"
rel="noopener noreferrer"
className="flex items-center hover:opacity-80 transition-opacity"
Expand Down
Loading