Cross-platform gstack enforcement hook
Design decisions
- Required mode generates
.claude/hooks/check-gstack.cjs. The explicit CommonJS extension is unaffected by a consumer repository'spackage.jsonmodule type. - The one registered command launches Node 18+ and resolves
CLAUDE_PROJECT_DIRinside JavaScript throughprocess.env. It contains no Bash, PowerShell, or cmd environment expansion. - The launcher validates
CLAUDE_PROJECT_DIRbefore callingpath.join. Missing or empty project context emits a structuredPreToolUsedenial, writes a clear reason to stderr, and exits zero instead of surfacing a hook execution error. - Path resolution and module loading run inside the same
try/catch. A nonexistent project, an existing project without the generated hook, or any other load failure returns the same concise denial without exposing a stack trace or the full project path. - The generated hook resolves the user home in
HOME,USERPROFILE, thenos.homedir()order so POSIX, Git Bash, PowerShell, and cmd runners use the expected global install root. - The hook contract follows
docs/spikes/claude-code-hook-mutation.md: pass-through writes{}; denial writes ahookSpecificOutputobject forPreToolUsewithpermissionDecision: "deny". - Missing or unreadable gstack is an intentional denial with exit code zero. Install instructions are present in stderr and
permissionDecisionReason, so the runner does not report a hook execution error. - Rerunning required mode replaces a matching legacy
check-gstack.shregistration, removes duplicate matching registrations, and deletes the obsolete Bash hook only after the CommonJS hook is generated and registered successfully. Unrelated project hooks remain unchanged.
Deviations
None. The implementation uses the requested Node launcher, one hook handler, and the repository's documented hook output schema.
Tradeoffs
- The generated project now requires Node 18+ at hook execution time. This removes shell selection ambiguity and matches the requested runtime floor.
- The generated hook is invoked through Node rather than as an executable file, so POSIX executable mode bits are no longer part of the contract.
Open questions
None.