Two guards, one mistake: matching text instead of shell structure

Both defects in this change come from asking a question about a command that only the shell grammar can answer, and answering it by looking at raw characters. One guard matched allowlist entries as literal prefixes; the other scanned text that the shell never executes.

1. A global option pushed an allowed subcommand off the allowlist (#3732)

The allowlist stores strings like git diff and matches with startsWith. Git accepts global options before the subcommand, so git -C <dir> diff does not start with git diff. In a worktree session that is the normal way to address a repository without cd, and cd is a compound command a read-only agent may not run, so the two rules together left no allowlisted way in.

before (literal prefix)
?
after (global options stripped)
?

now allowed

still denied

-c name=value is deliberately NOT stripped: it can set configuration that changes what the subcommand does, so it must not be waved through as a mere location flag.

2. A quoted heredoc body was scanned as if it were code (#3731)

A quoted heredoc disables every expansion, so its body is inert text on its way into a file. Scanning it meant that writing a runbook, a handoff, or an issue that quoted a destructive command was blocked, while the Write tool bypassed the same hook entirely. The guard never prevented the text existing; it only taught people the bypass.

before (scan raw text)
?
after (blank quoted bodies)
?

what the new scan actually inspects


    Switch the delimiter to an unquoted <<EOF and the body is scanned again, because an unquoted heredoc still performs parameter and command substitution.
  

Verified against the built bundle

Every row was run through bin/run-hook.mjs against the compiled dist rather than the TypeScript source, because a passing unit test says nothing about whether the fix reached what ships. Reverting either fix fails three of the new tests, so the tests are controls rather than decoration.