You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
bin/failproofai.mjs is the single entry point every user runs (failproofai, npx failproofai, the post-install hook, the bundled dist/cli.mjs). It parses --hook, --cli, --scope, the subcommand list (policies, login, relay, sync), routes to the right module, and standardises exit codes. We have great coverage of what those modules do once dispatched, but the dispatch layer itself currently has no direct unit test — regressions here ship straight to users (we already lived through this with #47 "clean CLI error handling — no raw stack traces, reject unknown args").
Adding a focused dispatch test suite keeps the CLI's user-facing contract (flag shapes, subcommand routing, exit codes, help/version) tied to a regression test.
Where
Entry: bin/failproofai.mjs (~530 lines)
Helpers used by entry: src/cli-error.ts, src/hooks/handler.ts (handleHookEvent), src/hooks/manager.ts, src/auth/login.ts, src/relay/{daemon,pid}.ts
No matching test file under __tests__/ for bin/
Why this matters
flowchart TB
USER[failproofai cmd] --> BIN[bin/failproofai.mjs]
BIN -->|--hook X| H[handler.ts ✅ tested]
BIN -->|policies| M[manager.ts ✅ tested]
BIN -->|login| L[auth/login.ts 🟡 partial]
BIN -->|relay| R[relay/daemon.ts 🟡 partial]
BIN -->|--version / --help| V[stdout / exit 0 ❓ untested]
BIN -->|unknown| E[cli-error → exit 1 ❓ untested]
BIN -->|--cli claude codex| MC[multi-value flag ❓ untested]
Loading
The grey boxes are where users live — and where regressions cause "the CLI prints a stack trace" / "—help silently exits 0 with no output" / "--cli claude codex only registered claude" issues.
Locks in the contract: exit code 0 = ok, 1 = user error, 2 = system error. A regression to "always 1" silently breaks scripts that gate on failproofai relay status.
Catches typo suggestions: an unknown flag should produce did you mean --scope? (similar to npm, git). A test guarantees this stays.
Documents the surface: the test matrix doubles as living documentation for what arg shapes we support.
Proposed enhancement
Create __tests__/bin/failproofai.test.ts:
import{spawnSync}from"node:child_process";import{join}from"node:path";constBIN=join(__dirname,"..","..","bin","failproofai.mjs");construn=(args: string[],env: NodeJS.ProcessEnv={})=>spawnSync("bun",[BIN, ...args],{env: { ...process.env, ...env},encoding: "utf8"});describe("bin/failproofai.mjs dispatch",()=>{it("--version prints version and exits 0",()=>{const{ stdout, status }=run(["--version"]);expect(status).toBe(0);expect(stdout).toMatch(/^\d+\.\d+\.\d+/);});it.each([["--help"],["help"],["policies","--help"],])("%s prints usage and exits 0",(...args)=>{const{ stdout, status }=run(args);expect(status).toBe(0);expect(stdout).toMatch(/Usage:/);});it("unknown flag exits 1 with a helpful message (not a stack trace)",()=>{const{ stderr, status }=run(["--scop","user"]);expect(status).toBe(1);expect(stderr).not.toMatch(/^\s+at/m);// no V8 stack framesexpect(stderr).toMatch(/unknown.*--scop/i);// user-friendlyexpect(stderr).toMatch(/didyoumean.*--scope/i);// suggestion});it.each([[["--cli","claude","codex","copilot"]],// space-separated[["--cli","claude","--cli","codex","--cli","copilot"]],// repeated])("--cli accepts %j",(args)=>{const{ status }=run(["policies","--list", ...args]);expect(status).toBe(0);});it("--hook PreToolUse routes to handler and propagates its exit code",()=>{const{ status }=run(["--hook","PreToolUse"],{/* feed empty stdin */});expect([0,2]).toContain(status);// 0 allow, 2 deny — never 1});});
Use vitest's bun runner via spawnSync; tests stay hermetic by setting HOME to a temp dir for any subcommand that touches ~/.failproofai.
Summary
bin/failproofai.mjsis the single entry point every user runs (failproofai,npx failproofai, the post-install hook, the bundleddist/cli.mjs). It parses--hook,--cli,--scope, the subcommand list (policies,login,relay,sync), routes to the right module, and standardises exit codes. We have great coverage of what those modules do once dispatched, but the dispatch layer itself currently has no direct unit test — regressions here ship straight to users (we already lived through this with #47 "clean CLI error handling — no raw stack traces, reject unknown args").Adding a focused dispatch test suite keeps the CLI's user-facing contract (flag shapes, subcommand routing, exit codes, help/version) tied to a regression test.
Where
bin/failproofai.mjs(~530 lines)src/cli-error.ts,src/hooks/handler.ts(handleHookEvent),src/hooks/manager.ts,src/auth/login.ts,src/relay/{daemon,pid}.ts__tests__/forbin/Why this matters
flowchart TB USER[failproofai cmd] --> BIN[bin/failproofai.mjs] BIN -->|--hook X| H[handler.ts ✅ tested] BIN -->|policies| M[manager.ts ✅ tested] BIN -->|login| L[auth/login.ts 🟡 partial] BIN -->|relay| R[relay/daemon.ts 🟡 partial] BIN -->|--version / --help| V[stdout / exit 0 ❓ untested] BIN -->|unknown| E[cli-error → exit 1 ❓ untested] BIN -->|--cli claude codex| MC[multi-value flag ❓ untested]The grey boxes are where users live — and where regressions cause "the CLI prints a stack trace" / "—help silently exits 0 with no output" / "
--cli claude codexonly registered claude" issues.This is distinct from:
as anyWebSocket cast with a small typed wrapper #154 (src/auth/andsrc/relay/module tests) — module-level, not dispatch-level.app/actions/#190 (dashboard server actions) — different surface entirely.vi.useFakeTimers/useRealTimersin try/finally (or afterEach) to prevent leaking fake timers across tests #173 (timer leakage in tests) — quality of existing tests.Why this helps users
0= ok,1= user error,2= system error. A regression to "always 1" silently breaks scripts that gate onfailproofai relay status.did you mean --scope?(similar tonpm,git). A test guarantees this stays.Proposed enhancement
Create
__tests__/bin/failproofai.test.ts:Use
vitest'sbunrunner viaspawnSync; tests stay hermetic by settingHOMEto a temp dir for any subcommand that touches~/.failproofai.Acceptance criteria
__tests__/bin/failproofai.test.tscovering: version, help, unknown-flag suggestion, multi-value--cliparsing (both shapes),--hookexit-code contract,--scopevalidation, missing required arg.Severity
Low — but the cost of regressions here is high (every customer touches the CLI). Cheap insurance against another #47-class issue.