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:
committed by
Steve Yegge
parent
37f465bde5
commit
dcf7b81011
@@ -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
35
internal/cmd/tap.go
Normal 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
116
internal/cmd/tap_guard.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user