From c09e846538e2616145568c32ab62591fff3b47e5 Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Fri, 10 Jul 2026 23:17:57 -0400 Subject: [PATCH] th-44bace: smooth uses smooth-agent plugin for guardrail hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable smooth-agent@smooth via enabledPlugins + extraKnownMarketplaces and delete the local .claude/hooks/ copies (enforce-worktree, th-curl-hint, enforce-pearls-labels) — the plugin now provides them (PR #185), genericized so one source of truth guards every SmooAI repo. The SessionStart worktree warning moves to the plugin's session-worktree-warning.sh; the pearls-workflow context stays global via th prime. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_015zP7baF7SHR89bPftsX1kp --- .claude/hooks/enforce-pearls-labels.sh | 20 ------ .claude/hooks/enforce-worktree.sh | 98 -------------------------- .claude/hooks/th-curl-hint.sh | 94 ------------------------ .claude/settings.json | 56 +++------------ 4 files changed, 9 insertions(+), 259 deletions(-) delete mode 100755 .claude/hooks/enforce-pearls-labels.sh delete mode 100755 .claude/hooks/enforce-worktree.sh delete mode 100755 .claude/hooks/th-curl-hint.sh diff --git a/.claude/hooks/enforce-pearls-labels.sh b/.claude/hooks/enforce-pearls-labels.sh deleted file mode 100755 index ac812438..00000000 --- a/.claude/hooks/enforce-pearls-labels.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# Warn when th pearls create is called without labels. -# Runs as PostToolUse on Bash — provides feedback after the command runs. -# Exit 0 = allow (with optional stderr feedback) - -TOOL_INPUT="$TOOL_INPUT" - -# Only check th pearls create commands -if ! echo "$TOOL_INPUT" | grep -q 'th pearls create'; then - exit 0 -fi - -# Check if --add-label was included -if echo "$TOOL_INPUT" | grep -qE '\-l\s|\-\-labels|\-\-add-label'; then - exit 0 -fi - -# Warn (exit 0 so it doesn't block, but stderr gives feedback) -echo "WARNING: 'th pearls create' was called without labels. Please add labels: th pearls update -l . Available: ai, approval, bugfix, config, database, docs, frontend, game, infra, integration, knowledge, marketing, pricing, realtime, sdk, security, setup, sme-review, social-media, testing" >&2 -exit 0 diff --git a/.claude/hooks/enforce-worktree.sh b/.claude/hooks/enforce-worktree.sh deleted file mode 100755 index dce24a8d..00000000 --- a/.claude/hooks/enforce-worktree.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# Enforce worktree workflow: block feature work on main branch in the main worktree. -# This hook runs on PreToolUse for Edit, Write, and Bash (git commit) events. -# Exit 0 = allow, Exit 1 = ask user permission, Exit 2 = hard block - -MAIN_WORKTREE="$HOME/dev/smooai/smooth" -WORKTREE_PARENT="$HOME/dev/smooai" -BYPASS_FILE="$MAIN_WORKTREE/.claude/worktree-bypass" - -# Session bypass: if the bypass file exists, allow everything. -if [[ -f "$BYPASS_FILE" ]]; then - exit 0 -fi - -# Read the event from stdin -INPUT=$(cat) -TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) -TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input // empty' 2>/dev/null) - -# Helper: check if a path is inside a feature worktree (not the main worktree) -is_in_worktree() { - local path="$1" - if [[ "$path" == "$WORKTREE_PARENT/smooth-"* ]]; then - return 0 - fi - return 1 -} - -# For Edit/Write: block source code changes targeting the main worktree -if [[ "$TOOL_NAME" == "Edit" || "$TOOL_NAME" == "Write" ]]; then - FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.file_path // empty' 2>/dev/null) - # Allow if the file is in a feature worktree - if is_in_worktree "$FILE_PATH"; then - exit 0 - fi - # Allow changes to .claude/, .beads/, .changeset/, CLAUDE.md, memory files - if [[ "$FILE_PATH" == *"/.claude/"* || "$FILE_PATH" == *"/.beads/"* || "$FILE_PATH" == *"/.changeset/"* || "$FILE_PATH" == *"CLAUDE.md"* || "$FILE_PATH" == *"/memory/"* ]]; then - exit 0 - fi - # Allow edits to files outside the smooth repo entirely - if [[ "$FILE_PATH" != "$MAIN_WORKTREE/"* ]]; then - exit 0 - fi - # Only block if we're actually on main in the main worktree - BRANCH=$(git -C "$MAIN_WORKTREE" symbolic-ref --short HEAD 2>/dev/null) - if [[ "$BRANCH" != "main" && "$BRANCH" != "master" ]]; then - exit 0 - fi - # Allow edits during an active merge (conflict resolution) - if [[ -f "$MAIN_WORKTREE/.git/MERGE_HEAD" ]]; then - exit 0 - fi - # Ask permission for source code edits on main - cat >&2 <<'EOF' -⚠️ You are about to edit source code directly on the main branch. - -ASK THE USER: "Should I make this change directly on main, or create a worktree?" - -If they say worktree, create one: - git worktree add ../smooth-SMOODEV-XX-short-desc -b SMOODEV-XX-short-desc main -EOF - exit 1 -fi - -# For Bash: block git commit on main (but allow merges, pulls, pushes, and worktree commits) -if [[ "$TOOL_NAME" == "Bash" ]]; then - COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // empty' 2>/dev/null) - - # Allow if the command targets a worktree via git -C or cd - if echo "$COMMAND" | grep -qE 'git\s+-C\s+.*/smooth-'; then - exit 0 - fi - if echo "$COMMAND" | grep -qE 'cd\s+.*/smooth-.*&&.*git\s+commit'; then - exit 0 - fi - - # Block git commit on main (unless it's a merge --no-ff or we're resolving a merge) - if echo "$COMMAND" | grep -qE 'git\s+commit' && ! echo "$COMMAND" | grep -q '\-\-no-ff'; then - # Allow commits during an active merge (conflict resolution) - if [[ -f "$MAIN_WORKTREE/.git/MERGE_HEAD" ]]; then - exit 0 - fi - # Check if we're on main - BRANCH=$(git -C "$MAIN_WORKTREE" symbolic-ref --short HEAD 2>/dev/null) - if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then - cat >&2 <<'EOF' -⚠️ You are about to commit directly to the main branch. - -ASK THE USER: "Should I commit this directly on main, or use a worktree?" - -Commits on main typically happen via merge (git merge BRANCH --no-ff). -EOF - exit 1 - fi - fi -fi - -exit 0 diff --git a/.claude/hooks/th-curl-hint.sh b/.claude/hooks/th-curl-hint.sh deleted file mode 100755 index f12952b9..00000000 --- a/.claude/hooks/th-curl-hint.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash -# th-curl-hint: PreToolUse Bash hook that nudges the agent toward the `th` CLI -# whenever the command about to run is a raw curl against a Smoo platform endpoint -# that already has a `th` wrapper. Also flags two well-known footguns (sst secret -# list, gh secret set with stdin echo) toward their scripts/secret-helpers wrappers. -# -# Exit codes: 0 allow silently, 1 ask the user (with stderr hint visible to Claude), -# 2 hard block. We use 1 — non-blocking nudge with a clear hint, override by confirming. -# -# Background: docs/Engineering/Using-th-CLI.md · pearl th-500495 / th-8b3d79 - -INPUT=$(cat) -TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) -[[ "$TOOL_NAME" != "Bash" ]] && exit 0 - -CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null) -[[ -z "$CMD" ]] && exit 0 - -# Bypass: if the message body itself mentions `th-curl-hint:ack` (e.g. the agent -# explained why curl is the right call), let it through. -if echo "$CMD" | grep -q 'th-curl-hint:ack'; then - exit 0 -fi - -emit() { - cat >&2 </agents → th api agents list [--org ] - /organizations//knowledge → th api knowledge list - /organizations//config/… → th api config (schemas|environments|values|feature-flag) - /organizations//jobs → th api jobs list - /organizations//members → th api members list - /organizations//auth-clients → th api keys list (dashboard auth required) - /admin/… → th admin … (planned — see pearl th-feebd2) -Full surface: th api help" - exit 1 -fi - -# --- atlassian.net Jira REST ---------------------------------------------------- -if echo "$CMD" | grep -qE 'curl[^|;&]+atlassian\.net/rest/api'; then - emit "raw curl against Jira REST" \ - "For read paths use \`th jira sync --pull\` followed by \`th pearls list / show\`. -Write verbs (create issue, transition status) aren't wrapped yet — if that's what -you need, this is the case where the override is fine. File a pearl on the smooth -repo so the next person doesn't have to curl." - exit 1 -fi - -# --- gh secret set with stdin echo (newline corruption — SMOODEV-879/909) ------- -if echo "$CMD" | grep -qE '(echo|printf)[^|]+\|\s*gh\s+secret\s+set'; then - emit "gh secret set with stdin echo — trailing-newline footgun" \ - "Use scripts/secret-helpers/gh-secret-set instead. The echo/printf pipeline -stores \"value\\n\" and silently breaks byte-comparing consumers (OAuth client_secret, -argon2 hashes — SMOODEV-879 burned us twice). The wrapper strips trailing whitespace -and refuses empty or mid-string newlines." - exit 1 -fi - -# --- pnpm sst secret list (plaintext leakage — SMOODEV-908) --------------------- -if echo "$CMD" | grep -qE 'pnpm\s+sst\s+secret\s+list' && ! echo "$CMD" | grep -q 'sst-secret-list'; then - emit "raw \`pnpm sst secret list\` leaks every secret as plaintext" \ - "Use scripts/secret-helpers/sst-secret-list --stage instead. It redacts -values by default; pass --reveal only when you need them. The raw command prints -Name=value pairs that leak hard in screenshares / Slack / transcripts (SMOODEV-908)." - exit 1 -fi - -exit 0 diff --git a/.claude/settings.json b/.claude/settings.json index 33885d41..8dadd230 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,52 +1,14 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", - "hooks": { - "PreToolUse": [ - { - "matcher": "Edit|Write", - "hooks": [ - { - "type": "command", - "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/enforce-worktree.sh" - } - ] - }, - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/enforce-worktree.sh" - }, - { - "type": "command", - "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/th-curl-hint.sh" - } - ] + "extraKnownMarketplaces": { + "smooth": { + "source": { + "source": "github", + "repo": "SmooAI/smooth" } - ], - "PostToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/enforce-pearls-labels.sh" - } - ] - } - ], - "SessionStart": [ - { - "matcher": "startup", - "hooks": [ - { - "type": "command", - "command": "BRANCH=$(git -C \"$HOME/dev/smooai/smooth\" symbolic-ref --short HEAD 2>/dev/null); DIR=$(pwd); MAIN=\"$HOME/dev/smooai/smooth\"; if [[ \"$DIR\" == \"$MAIN\" || \"$DIR\" == \"$MAIN/\"* ]] && [[ \"$BRANCH\" == \"main\" ]]; then echo '⚠️ You are in the MAIN worktree (~/dev/smooai/smooth/) on the main branch. Do NOT do feature work here. Create a worktree first: git worktree add ../smooth-SMOODEV-XX-desc -b SMOODEV-XX-desc main'; fi" - } - ] - } - ] + } }, - "enabledPlugins": {} + "enabledPlugins": { + "smooth-agent@smooth": true + } }