refactor(hooks): rename to gt tap guard pr-workflow

Reorganizes Claude Code hook handlers under `gt tap` namespace:
- gt tap - parent command for all hook handlers
- gt tap guard - subcommand for blocking operations
- gt tap guard pr-workflow - blocks PR creation and feature branches

This structure allows future expansion:
- gt tap audit <x>   - logging/metrics (PostToolUse)
- gt tap inject <x>  - input modification (PreToolUse)
- gt tap check <x>   - validation (PostToolUse)

Replaces the flat gt block-pr-workflow command.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/emma
2026-01-19 11:21:54 -08:00
committed by Steve Yegge
parent 37f465bde5
commit dcf7b81011
3 changed files with 151 additions and 106 deletions

View File

@@ -1,106 +0,0 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
)
var blockPRWorkflowCmd = &cobra.Command{
Use: "block-pr-workflow",
Hidden: true, // Internal command for Claude Code hooks
Short: "Block PR workflow operations (hook helper)",
Long: `Block PR workflow operations in Gas Town.
This command is called by Claude Code PreToolUse hooks to enforce the
"no PRs" policy. Gas Town workers push directly to main - PRs add friction
that breaks the autonomous execution model.
Exit codes:
0 - Operation allowed (not in a restricted context)
2 - Operation BLOCKED (hook will prevent tool execution)
The hook configuration in .claude/settings.json:
{
"PreToolUse": [{
"matcher": "Bash(gh pr create*)",
"hooks": [{"type": "command", "command": "gt block-pr-workflow --reason pr-create"}]
}]
}`,
RunE: runBlockPRWorkflow,
}
var blockPRReason string
func init() {
blockPRWorkflowCmd.Flags().StringVar(&blockPRReason, "reason", "", "Reason for the block check (pr-create, feature-branch)")
rootCmd.AddCommand(blockPRWorkflowCmd)
}
func runBlockPRWorkflow(cmd *cobra.Command, args []string) error {
// Check if we're in a Gas Town agent context
// These env vars indicate we're running as a managed agent
isPolecat := os.Getenv("GT_POLECAT") != ""
isCrew := os.Getenv("GT_CREW") != ""
isWitness := os.Getenv("GT_WITNESS") != ""
isRefinery := os.Getenv("GT_REFINERY") != ""
isMayor := os.Getenv("GT_MAYOR") != ""
isDeacon := os.Getenv("GT_DEACON") != ""
// Also check if we're in a crew worktree by path
cwd, _ := os.Getwd()
inCrewWorktree := strings.Contains(cwd, "/crew/")
inPolecatWorktree := strings.Contains(cwd, "/polecats/")
isGasTownAgent := isPolecat || isCrew || isWitness || isRefinery || isMayor || isDeacon || inCrewWorktree || inPolecatWorktree
if !isGasTownAgent {
// Not in a Gas Town managed context - allow the operation
// This lets humans use PRs if they want
return nil
}
// We're in a Gas Town context - block PR operations
switch blockPRReason {
case "pr-create":
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "╔══════════════════════════════════════════════════════════════════╗")
fmt.Fprintln(os.Stderr, "║ ❌ PR CREATION BLOCKED ║")
fmt.Fprintln(os.Stderr, "╠══════════════════════════════════════════════════════════════════╣")
fmt.Fprintln(os.Stderr, "║ Gas Town workers push directly to main. PRs are forbidden. ║")
fmt.Fprintln(os.Stderr, "║ ║")
fmt.Fprintln(os.Stderr, "║ Instead of: gh pr create ... ║")
fmt.Fprintln(os.Stderr, "║ Do this: git push origin main ║")
fmt.Fprintln(os.Stderr, "║ ║")
fmt.Fprintln(os.Stderr, "║ Why? PRs add friction that breaks autonomous execution. ║")
fmt.Fprintln(os.Stderr, "║ See: ~/gt/docs/PRIMING.md (GUPP principle) ║")
fmt.Fprintln(os.Stderr, "╚══════════════════════════════════════════════════════════════════╝")
fmt.Fprintln(os.Stderr, "")
os.Exit(2) // Exit 2 = BLOCK in Claude Code hooks
case "feature-branch":
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "╔══════════════════════════════════════════════════════════════════╗")
fmt.Fprintln(os.Stderr, "║ ⚠️ FEATURE BRANCH BLOCKED ║")
fmt.Fprintln(os.Stderr, "╠══════════════════════════════════════════════════════════════════╣")
fmt.Fprintln(os.Stderr, "║ Gas Town workers commit directly to main. No feature branches. ║")
fmt.Fprintln(os.Stderr, "║ ║")
fmt.Fprintln(os.Stderr, "║ Instead of: git checkout -b feature/... ║")
fmt.Fprintln(os.Stderr, "║ Do this: git add . && git commit && git push origin main ║")
fmt.Fprintln(os.Stderr, "║ ║")
fmt.Fprintln(os.Stderr, "║ Why? Feature branches lead to PRs. We push directly to main. ║")
fmt.Fprintln(os.Stderr, "╚══════════════════════════════════════════════════════════════════╝")
fmt.Fprintln(os.Stderr, "")
os.Exit(2)
default:
// Unknown reason but we're in Gas Town context - block conservatively
fmt.Fprintf(os.Stderr, "❌ Operation blocked by Gas Town policy (reason: %s)\n", blockPRReason)
fmt.Fprintln(os.Stderr, "Gas Town workers push directly to main. See ~/gt/docs/PRIMING.md")
os.Exit(2)
}
return nil
}

35
internal/cmd/tap.go Normal file
View File

@@ -0,0 +1,35 @@
package cmd
import (
"github.com/spf13/cobra"
)
var tapCmd = &cobra.Command{
Use: "tap",
Short: "Claude Code hook handlers",
Long: `Hook handlers for Claude Code PreToolUse and PostToolUse events.
These commands are called by Claude Code hooks to implement policies,
auditing, and input transformation. They tap into the tool execution
flow to guard, audit, inject, or check.
Subcommands:
guard - Block forbidden operations (PreToolUse, exit 2)
audit - Log/record tool executions (PostToolUse) [planned]
inject - Modify tool inputs (PreToolUse, updatedInput) [planned]
check - Validate after execution (PostToolUse) [planned]
Hook configuration in .claude/settings.json:
{
"PreToolUse": [{
"matcher": "Bash(gh pr create*)",
"hooks": [{"command": "gt tap guard pr-workflow"}]
}]
}
See ~/gt/docs/HOOKS.md for full documentation.`,
}
func init() {
rootCmd.AddCommand(tapCmd)
}

116
internal/cmd/tap_guard.go Normal file
View File

@@ -0,0 +1,116 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
)
var tapGuardCmd = &cobra.Command{
Use: "guard",
Short: "Block forbidden operations (PreToolUse hook)",
Long: `Block forbidden operations via Claude Code PreToolUse hooks.
Guard commands exit with code 2 to BLOCK tool execution when a policy
is violated. They're called before the tool runs, preventing the
forbidden operation entirely.
Available guards:
pr-workflow - Block PR creation and feature branches
Example hook configuration:
{
"PreToolUse": [{
"matcher": "Bash(gh pr create*)",
"hooks": [{"command": "gt tap guard pr-workflow"}]
}]
}`,
}
var tapGuardPRWorkflowCmd = &cobra.Command{
Use: "pr-workflow",
Short: "Block PR creation and feature branches",
Long: `Block PR workflow operations in Gas Town.
Gas Town workers push directly to main. PRs add friction that breaks
the autonomous execution model (GUPP principle).
This guard blocks:
- gh pr create
- git checkout -b (feature branches)
- git switch -c (feature branches)
Exit codes:
0 - Operation allowed (not in Gas Town agent context)
2 - Operation BLOCKED (in agent context)
The guard only blocks when running as a Gas Town agent (crew, polecat,
witness, etc.). Humans running outside Gas Town can still use PRs.`,
RunE: runTapGuardPRWorkflow,
}
func init() {
tapCmd.AddCommand(tapGuardCmd)
tapGuardCmd.AddCommand(tapGuardPRWorkflowCmd)
}
func runTapGuardPRWorkflow(cmd *cobra.Command, args []string) error {
// Check if we're in a Gas Town agent context
if !isGasTownAgentContext() {
// Not in a Gas Town managed context - allow the operation
return nil
}
// We're in a Gas Town context - block PR operations
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "╔══════════════════════════════════════════════════════════════════╗")
fmt.Fprintln(os.Stderr, "║ ❌ PR WORKFLOW BLOCKED ║")
fmt.Fprintln(os.Stderr, "╠══════════════════════════════════════════════════════════════════╣")
fmt.Fprintln(os.Stderr, "║ Gas Town workers push directly to main. PRs are forbidden. ║")
fmt.Fprintln(os.Stderr, "║ ║")
fmt.Fprintln(os.Stderr, "║ Instead of: gh pr create / git checkout -b / git switch -c ║")
fmt.Fprintln(os.Stderr, "║ Do this: git add . && git commit && git push origin main ║")
fmt.Fprintln(os.Stderr, "║ ║")
fmt.Fprintln(os.Stderr, "║ Why? PRs add friction that breaks autonomous execution. ║")
fmt.Fprintln(os.Stderr, "║ See: ~/gt/docs/PRIMING.md (GUPP principle) ║")
fmt.Fprintln(os.Stderr, "╚══════════════════════════════════════════════════════════════════╝")
fmt.Fprintln(os.Stderr, "")
os.Exit(2) // Exit 2 = BLOCK in Claude Code hooks
return nil
}
// isGasTownAgentContext returns true if we're running as a Gas Town managed agent.
func isGasTownAgentContext() bool {
// Check environment variables set by Gas Town session management
envVars := []string{
"GT_POLECAT",
"GT_CREW",
"GT_WITNESS",
"GT_REFINERY",
"GT_MAYOR",
"GT_DEACON",
}
for _, env := range envVars {
if os.Getenv(env) != "" {
return true
}
}
// Also check if we're in a crew or polecat worktree by path
cwd, err := os.Getwd()
if err != nil {
return false
}
agentPaths := []string{"/crew/", "/polecats/"}
for _, path := range agentPaths {
if strings.Contains(cwd, path) {
return true
}
}
return false
}