ok commit-message-lint :: conventional subject (fix, no scope) is allowed ok commit-message-lint :: non-conventional subject is denied ok commit-message-lint :: scoped conventional subject feat(api): is allowed ok commit-message-lint :: breaking-change subject feat!: is allowed ok commit-message-lint :: --message=value form is parsed and validated the same as -m ok commit-message-lint :: with multiple -m flags, only the first (the subject) is validated ok commit-message-lint :: bare 'git commit' (editor flow, no inline message) is allowed silently ok commit-message-lint :: 'git commit --amend --no-edit' has no inline message and is allowed ok commit-message-lint :: 'git commit -C HEAD~1' reuses a prior message and is allowed ok commit-message-lint :: compound command hides commit behind && but is still caught ok commit-message-lint :: quoted 'git commit -m' inside echo is not a git call ok commit-message-lint :: FR_COMMIT_LINT_OFF=1 allows a subject that would otherwise be denied ok commit-message-lint :: invalid JSON stdin exits 0 silently (fail open) ok commit-message-lint :: unparseable FR_COMMIT_RE fails open (exit 1, one stderr note) instead of blocking ok context-loader :: branch comes from .git/HEAD even when git commands fail (PATH empty) ok context-loader :: cwd without .git emits nothing ok context-loader :: FR_CONTEXT_OFF=1 disables the loader ok context-loader :: invalid JSON stdin exits 0 silently (fail open) ok context-loader :: detached HEAD reported with short hash ok context-loader :: dirty file count via real git on minimal repo (requires git on PATH) ok context-loader :: .git pointer file (worktree/submodule) resolves to the real HEAD ok context-loader :: branch names containing slashes survive HEAD parsing ok context-loader :: stdin without cwd falls back to process cwd ok cost-logger :: sums message.usage tokens across transcript lines ok cost-logger :: unparsable lines and non-integer counts are skipped ok cost-logger :: missing transcript logs a zeros line ok cost-logger :: header row is written when the ledger is created ok cost-logger :: appends to an existing ledger without rewriting it ok cost-logger :: FR_COST_LOG_OFF=1 disables the logger ok cost-logger :: invalid JSON stdin exits 0 silently and writes nothing (fail open) ok cost-logger :: FR_COST_LOG parent dirs are created; missing cache count reads as 0 ok dependency-change-alert :: Edit on package.json alerts with structured JSON ok dependency-change-alert :: Write to package-lock.json alerts unconditionally ok dependency-change-alert :: Edit on an unrelated source file stays silent ok dependency-change-alert :: pyproject.toml edit touching dependencies alerts ok dependency-change-alert :: pyproject.toml edit unrelated to dependencies stays silent ok dependency-change-alert :: requirements-*.txt glob matches on Write ok dependency-change-alert :: FR_DEPALERT_OFF=1 disables the hook ok dependency-change-alert :: FR_DEPALERT_EXTRA adds a custom manifest glob ok dependency-change-alert :: invalid JSON stdin exits 0 silently (fail open) ok dependency-change-alert :: Bash-driven npm install bypasses Edit|Write and is not seen ok dependency-change-alert :: a backup/renamed basename evades matching even with dependency content ok destructive-bash-guard :: rm -rf / is denied with structured JSON ok destructive-bash-guard :: rm with separate -r -f flags on $HOME is denied ok destructive-bash-guard :: rm -rf node_modules (relative, inside project) is allowed ok destructive-bash-guard :: rm -rf on an absolute path inside the session cwd is allowed ok destructive-bash-guard :: rm -fr on an absolute path outside the session cwd is denied ok destructive-bash-guard :: rm -r without -f does not match (both flags required) ok destructive-bash-guard :: rm -rf on a path ending /* is denied ok destructive-bash-guard :: git push --force origin main is denied ok destructive-bash-guard :: git push -f origin HEAD:master (refspec form) is denied ok destructive-bash-guard :: git push -f to a feature branch is allowed ok destructive-bash-guard :: git push origin main without force is allowed ok destructive-bash-guard :: git clean -fdx at the repo root is denied ok destructive-bash-guard :: git clean -fdx outside the repo root (no .git in cwd) is allowed ok destructive-bash-guard :: dd of=/dev/sda is denied ok destructive-bash-guard :: dd of=/dev/null (benchmark idiom) is allowed ok destructive-bash-guard :: mkfs.ext4 is denied ok destructive-bash-guard :: sudo shutdown -h now is denied (wrapper stripped) ok destructive-bash-guard :: 'echo halted' does not substring-match the halt pattern ok destructive-bash-guard :: chmod -R 777 / is denied ok destructive-bash-guard :: the :(){ fork bomb is denied ok destructive-bash-guard :: redirection onto /dev/sda is denied ok destructive-bash-guard :: quoted mention of a dangerous command is allowed ok destructive-bash-guard :: dangerous string inside a heredoc body is allowed ok destructive-bash-guard :: danger hidden behind && in a chained command is denied ok destructive-bash-guard :: FR_DESTRUCTIVE_ALLOW skips the named pattern, others still fire ok destructive-bash-guard :: FR_DESTRUCTIVE_ALLOW with the only matching pattern allows the command ok destructive-bash-guard :: FR_DESTRUCTIVE_GUARD_OFF=1 disables the guard ok destructive-bash-guard :: invalid JSON stdin exits 0 silently (fail open) ok env-file-write-guard :: Write of .env is denied with structured JSON ok env-file-write-guard :: Write of .env.example is allowed (template, not secret) ok env-file-write-guard :: Edit of secrets.yml is denied ok env-file-write-guard :: Write of a *.key file is denied ok env-file-write-guard :: Bash redirect into .env is denied ok env-file-write-guard :: Bash append (>>) into .npmrc is denied ok env-file-write-guard :: Bash tee into .env is denied (pipe, wrapper, and -a flag) ok env-file-write-guard :: Bash cp onto .env is denied (even from a template source) ok env-file-write-guard :: Bash mv onto secrets.json is denied ok env-file-write-guard :: Bash redirect into a non-secret file is allowed (2>&1 not a target) ok env-file-write-guard :: Bash read of .env passes here (reads are secret-leak-guard's job) ok env-file-write-guard :: FR_ENVWRITE_GUARD_OFF=1 disables the guard ok env-file-write-guard :: FR_ENVWRITE_ALLOW adds custom allow glob ok env-file-write-guard :: invalid JSON stdin exits 0 silently (fail open) ok env-file-write-guard :: Write of .env.production is denied (.env.* glob) ok env-file-write-guard :: Bash fd redirect (2> .env) is denied (still truncates the file) ok format-on-write :: FR_FORMAT_CMD template runs and rewrites the edited file ok format-on-write :: {file} substitution is shell-quoted (path with spaces) ok format-on-write :: FR_FORMAT_OFF=1 disables formatting entirely ok format-on-write :: tool_input without file_path is a silent no-op ok format-on-write :: file_path that does not exist on disk is a silent no-op ok format-on-write :: extension with no formatter mapping leaves the file untouched ok format-on-write :: .py with neither ruff nor black on PATH is a silent no-op ok format-on-write :: .md without project-local prettier is untouched (no global fallback) ok format-on-write :: failing formatter is swallowed: exit 0, no output, file untouched ok format-on-write :: invalid JSON stdin exits 0 silently (fail open) ok format-on-write :: valid JSON that is not an object exits 0 silently (fail open) ok git-main-guard :: git commit on main is denied with structured JSON ok git-main-guard :: git commit on a feature branch passes silently ok git-main-guard :: bare git push on main is denied ok git-main-guard :: push refspec HEAD:main from a feature branch is denied ok git-main-guard :: pushing a non-protected refspec while on main passes ok git-main-guard :: git push --all is denied even from a feature branch ok git-main-guard :: detached HEAD (raw sha) allows commit silently ok git-main-guard :: no .git directory at all allows commit silently ok git-main-guard :: FR_PROTECTED_BRANCHES=develop:release denies commit on develop ok git-main-guard :: exported FR_MAIN_OK=1 allows commit on main ok git-main-guard :: inline FR_MAIN_OK=1 prefix allows push on main ok git-main-guard :: compound command hides commit behind && but is still caught ok git-main-guard :: quoted 'git push' inside echo is not a git call ok git-main-guard :: invalid JSON stdin exits 0 silently (fail open) ok git-main-guard :: delete refspec :main is denied even from a feature branch ok git-main-guard :: linked worktree (.git file with gitdir pointer) resolves the branch ok lint-on-stop :: passing lint (FR_LINT_CMD=true) allows the stop silently ok lint-on-stop :: failing lint blocks the stop with the lint output ok lint-on-stop :: no lint command detectable: allow silently, run nothing ok lint-on-stop :: FR_LINT_OFF=1 disables the hook even with a failing lint ok lint-on-stop :: invalid JSON stdin exits 0 silently (fail open) ok lint-on-stop :: one prior block in this session: still blocks, counter reaches 2 ok lint-on-stop :: two prior blocks in this session: allow with warning, do not block ok lint-on-stop :: passing lint resets the loop-guard counter ok lint-on-stop :: loop-guard state is per session: another session's counter is ignored ok lint-on-stop :: FR_LINT_TIMEOUT hit: allow the stop with a warning, no block ok lint-on-stop :: block reason is truncated to the last 2000 chars of lint output ok lint-on-stop :: package.json scripts.lint autodetects npm run; failing script blocks ok lint-on-stop :: ruff.toml present but ruff binary not on PATH: allow silently ok notify-on-long-run :: stdin message reaches the notify command with the fixed title ok notify-on-long-run :: missing message field falls back to the default text ok notify-on-long-run :: whitespace-only message falls back to the default text ok notify-on-long-run :: non-string message field falls back to the default text ok notify-on-long-run :: FR_NOTIFY_OFF=1 disables the hook entirely ok notify-on-long-run :: invalid JSON stdin exits 0 silently, no notification fired (fail open) ok notify-on-long-run :: no FR_NOTIFY_CMD and no notifier on PATH exits 0 silently ok notify-on-long-run :: shell metacharacters in the message stay literal text ok notify-on-long-run :: failing notify command still exits 0 with a stderr debug note ok outbound-network-guard :: curl plain GET with no data flags passes untouched ok outbound-network-guard :: curl -d to a non-allowlisted host asks, naming host and verb ok outbound-network-guard :: FR_NET_ALLOW exact-match entry allows an upload to that host ok outbound-network-guard :: FR_NET_ALLOW leading-dot entry allows any matching subdomain ok outbound-network-guard :: a leading-dot allowlist entry does not also cover the bare apex domain ok outbound-network-guard :: wget --post-data to a non-allowlisted host asks ok outbound-network-guard :: FR_NET_DENY=1 escalates an ask to a deny for headless runs ok outbound-network-guard :: FR_NET_GUARD_OFF=1 disables the guard entirely ok outbound-network-guard :: a data-sending curl with no parseable http(s) URL asks (unknown destination) ok outbound-network-guard :: localhost is allowlisted by default ok outbound-network-guard :: a benign curl chained before a data-sending curl still gets caught ok outbound-network-guard :: curl -XPOST (attached form, no space) is recognized as a POST ok outbound-network-guard :: curl -X GET with no data flags is still a read, not flagged ok outbound-network-guard :: curl -T/--upload-file with no explicit -X defaults the verb label to PUT ok outbound-network-guard :: a sudo-wrapped curl invocation is still parsed and flagged ok outbound-network-guard :: an ordinary non-networking command passes through untouched ok outbound-network-guard :: invalid JSON stdin exits 0 silently (fail open) ok outbound-network-guard :: wget --method=PUT (no other send flag) is still a trigger ok outbound-network-guard :: one non-allowlisted host among several URL args still asks ok outbound-network-guard :: non-Bash tool events are ignored entirely ok outbound-network-guard :: hostname extraction strips userinfo and port before allowlist matching ok outbound-network-guard :: documented limitation: -dfoo=bar (attached, no separator) is not recognized as a data flag ok pr-checklist-on-stop :: first stop of a session blocks with checklist content and writes a marker ok pr-checklist-on-stop :: marker file from an earlier block: later stop allows silently, no nagging ok pr-checklist-on-stop :: stop_hook_active true: report via systemMessage, never block, no marker written ok pr-checklist-on-stop :: no checklist file at all: opt-in via file existence, exits silently ok pr-checklist-on-stop :: checklist file with only blank lines counts as empty: exits silently ok pr-checklist-on-stop :: FR_CHECKLIST_FILE overrides the default path (resolved relative to session cwd) ok pr-checklist-on-stop :: FR_CHECKLIST_OFF=1 disables the hook entirely ok pr-checklist-on-stop :: invalid JSON stdin exits 0 silently (fail open) ok pr-checklist-on-stop :: state dir path collides with an existing file: unwritable, treated as already blocked ok pr-checklist-on-stop :: checklist content over the 1500-char cap is truncated in the block reason ok secret-leak-guard :: Read of .env is denied with structured JSON ok secret-leak-guard :: .env.example passes silently ok secret-leak-guard :: Bash cat .env is denied ok secret-leak-guard :: cp .env.example .env passes (no read verb) ok secret-leak-guard :: Read of a .pem key is denied ok secret-leak-guard :: Bash grep in ~/.ssh/ is denied via path fragment ok secret-leak-guard :: git status passes silently ok secret-leak-guard :: FR_SECRET_GUARD_OFF=1 disables the guard ok secret-leak-guard :: invalid JSON stdin exits 0 silently (fail open) ok secret-leak-guard :: FR_SECRET_EXTRA adds custom deny glob ok test-on-stop :: passing tests (FR_TEST_CMD=true) allow the stop silently ok test-on-stop :: failing tests block the stop with the test output ok test-on-stop :: no test command detectable: allow silently, run nothing ok test-on-stop :: npm's default 'no test specified' placeholder script is not a test suite ok test-on-stop :: real scripts.test but npm not on PATH: skip silently, do not block on 'npm: not found' ok test-on-stop :: unparsable package.json: keep detecting, allow silently ok test-on-stop :: FR_TEST_OFF=1 disables the hook even with failing tests ok test-on-stop :: invalid JSON stdin exits 0 silently (fail open) ok test-on-stop :: valid JSON but not an object exits 0 silently (fail open) ok test-on-stop :: one prior block in this session: still blocks, counter reaches 2 ok test-on-stop :: two prior blocks in this session: allow with warning, do not block ok test-on-stop :: passing tests reset the loop-guard counter ok test-on-stop :: loop-guard state is per session: another session's counter is ignored ok test-on-stop :: FR_TEST_TIMEOUT hit: allow the stop with a warning, no block ok test-on-stop :: unwritable FR_STATE_DIR: allow with warning instead of risking a block loop ok todo-scan-on-stop :: added TODO and console.log block the stop with file-attributed list ok todo-scan-on-stop :: markers on removed and context lines never trigger ok todo-scan-on-stop :: +++ header naming TODO.md is not treated as an added line ok todo-scan-on-stop :: default console\.log pattern does not match consolezlog (dot is escaped) ok todo-scan-on-stop :: FR_TODO_PATTERNS replaces the defaults: HACK and case-insensitive wip hit, TODO does not ok todo-scan-on-stop :: marker file from an earlier block: allow the stop with a systemMessage ok todo-scan-on-stop :: stop_hook_active true: report via systemMessage, never block, no marker written ok todo-scan-on-stop :: block marker is per session: another session's marker does not suppress the block ok todo-scan-on-stop :: identical added line from overlapping tree and cached diffs counts once ok todo-scan-on-stop :: more than 15 hits: list is capped with an overflow count ok todo-scan-on-stop :: diff command exiting non-zero allows the stop silently (fail open) ok todo-scan-on-stop :: default git diff command outside a git repo allows the stop silently ok todo-scan-on-stop :: FR_TODO_OFF=1 disables the scan entirely ok todo-scan-on-stop :: invalid JSON stdin exits 0 silently (fail open) ok transcript-archiver :: archives transcript and records raw_bytes/lines/cwd in the index ok transcript-archiver :: header row is written when the index is created ok transcript-archiver :: appends to an existing index without rewriting it ok transcript-archiver :: FR_ARCHIVE_MAX_MB=0 skips the gzip copy but still indexes with archive_file empty ok transcript-archiver :: FR_ARCHIVE_OFF=1 disables archiving entirely ok transcript-archiver :: invalid JSON stdin exits 0 silently (fail open) ok transcript-archiver :: missing transcript file exits 0 silently, nothing indexed ok transcript-archiver :: transcript_path pointing at a directory is treated as unreadable, exits 0 silently ok transcript-archiver :: unsafe characters in session_id are sanitized in the archive filename ok transcript-archiver :: FR_ARCHIVE_DIR parent directories are created as needed ok typecheck-on-write :: FR_TYPECHECK_CMD failure surfaces additionalContext ok typecheck-on-write :: {file} substitution is shell-quoted (path with spaces) ok typecheck-on-write :: FR_TYPECHECK_CMD success is silent (exit 0, no context) ok typecheck-on-write :: FR_TYPECHECK_OFF=1 disables the hook even with a failing FR_TYPECHECK_CMD ok typecheck-on-write :: invalid JSON stdin exits 0 silently (fail open) ok typecheck-on-write :: .ts with no tsconfig.json anywhere is a silent no-op ok typecheck-on-write :: .ts with a tsconfig.json but no project-local node_modules/.bin/tsc is a silent no-op (never a global tsc) ok typecheck-on-write :: tsconfig.json above the stdin cwd is not picked up (walk never crosses above cwd) ok typecheck-on-write :: .py with a mypy config but mypy not on PATH is a silent no-op ok typecheck-on-write :: .py with no mypy config anywhere (mypy.ini / pyproject.toml / setup.cfg) is a silent no-op ok typecheck-on-write :: file_path pointing at a nonexistent file is a silent no-op ok typecheck-on-write :: tool_input without file_path is a silent no-op ok typecheck-on-write :: valid JSON that is not an object exits 0 silently (fail open) ok typecheck-on-write :: a typecheck that outlives FR_TYPECHECK_TIMEOUT is abandoned silently 230/230 cases passed across 17 hooks