go-agent/claude/agentic/scripts/suggest-core-cli.sh
Snider 61e01bfdf1 feat: initial go-agent — agentci + jobrunner + plugins marketplace
Consolidates three codebases into a single agent orchestration repo:

- agentci (from go-scm): Clotho dual-run verification, agent config,
  SSH security (sanitisation, secure commands, token masking)
- jobrunner (from go-scm): Poll-dispatch-report pipeline with 7 handlers
  (dispatch, completion, auto-merge, publish draft, dismiss reviews,
  send fix command, tick parent epic)
- plugins marketplace (from agentic/plugins): 27 Claude/Codex/Gemini
  plugins with shared MCP server

All 150+ tests passing across 6 packages.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-21 15:47:19 +00:00

277 lines
13 KiB
Bash
Executable file

#!/bin/bash
# PreToolUse Hook - Comprehensive core CLI suggestions and safety rails
# Intercepts commands and suggests safer core CLI equivalents
# Logs decisions for training data collection
set -euo pipefail
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')
session_id=$(echo "$input" | jq -r '.session_id // "unknown"')
# Log file for training data (wrong choices, blocked commands)
LOG_DIR="/home/shared/hostuk/training-data/command-intercepts"
mkdir -p "$LOG_DIR" 2>/dev/null || true
log_intercept() {
local action="$1"
local raw_cmd="$2"
local suggestion="$3"
local reason="$4"
if [[ -d "$LOG_DIR" ]]; then
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
local log_file="$LOG_DIR/$(date +%Y-%m-%d).jsonl"
echo "{\"timestamp\":\"$timestamp\",\"session\":\"$session_id\",\"action\":\"$action\",\"raw_command\":$(echo "$raw_cmd" | jq -Rs .),\"suggestion\":\"$suggestion\",\"reason\":$(echo "$reason" | jq -Rs .)}" >> "$log_file" 2>/dev/null || true
fi
}
# If no command, allow
if [[ -z "$command" ]]; then
echo '{"continue": true}'
exit 0
fi
# Normalize command for matching
norm_cmd=$(echo "$command" | tr '[:upper:]' '[:lower:]')
# =============================================================================
# BLOCKED COMMANDS - Hard deny, these are always wrong
# =============================================================================
blocked_patterns=(
"rm -rf /|Refusing to delete root filesystem"
"rm -rf /*|Refusing to delete root filesystem"
"rm -rf ~|Refusing to delete home directory"
"rm -rf \$HOME|Refusing to delete home directory"
":(){ :|:& };:|Fork bomb detected"
"dd if=/dev/zero of=/dev/sd|Refusing to wipe disk"
"dd if=/dev/zero of=/dev/nvme|Refusing to wipe disk"
"mkfs|Refusing to format filesystem"
"fdisk|Refusing disk partitioning"
"> /dev/sd|Refusing to write to raw disk"
"chmod -R 777 /|Refusing recursive 777 on root"
"chmod 777 /|Refusing 777 on root"
"chown -R root /|Refusing recursive chown on root"
)
for entry in "${blocked_patterns[@]}"; do
pattern="${entry%%|*}"
reason="${entry#*|}"
if [[ "$command" == *"$pattern"* ]]; then
log_intercept "BLOCKED" "$command" "" "$reason"
cat << EOF
{
"continue": false,
"hookSpecificOutput": {
"permissionDecision": "deny"
},
"systemMessage": "🚫 **BLOCKED**: $reason\n\nThis command has been blocked for safety. If you believe this is a mistake, ask the user for explicit confirmation."
}
EOF
exit 0
fi
done
# =============================================================================
# DANGEROUS COMMANDS - Warn and require confirmation
# =============================================================================
dangerous_patterns=(
"git reset --hard|Discards ALL uncommitted changes permanently. Consider: git stash"
"git clean -f|Deletes untracked files permanently. Consider: git clean -n (dry run)"
"git clean -fd|Deletes untracked files AND directories permanently"
"git checkout .|Discards all uncommitted changes in working directory"
"git restore .|Discards all uncommitted changes in working directory"
"git branch -D|Force-deletes branch even if not merged. Consider: git branch -d"
"git push --force|Force push can overwrite remote history. Consider: core git push"
"git push -f |Force push can overwrite remote history. Consider: core git push"
"git rebase -i|Interactive rebase rewrites history - ensure you know what you're doing"
"docker system prune|Removes ALL unused containers, networks, images"
"docker volume prune|Removes ALL unused volumes - may delete data"
"docker container prune|Removes ALL stopped containers"
"npm cache clean --force|Clears entire npm cache"
"rm -rf node_modules|Deletes all dependencies - will need npm install"
"rm -rf vendor|Deletes all PHP dependencies - will need composer install"
"rm -rf .git|Deletes entire git history permanently"
"truncate|Truncates file to specified size - may lose data"
"find . -delete|Recursively deletes files - verify pattern first"
"find . -exec rm|Recursively deletes files - verify pattern first"
"xargs rm|Mass deletion - verify input first"
)
for entry in "${dangerous_patterns[@]}"; do
pattern="${entry%%|*}"
reason="${entry#*|}"
if [[ "$command" == *"$pattern"* ]]; then
log_intercept "DANGEROUS" "$command" "" "$reason"
cat << EOF
{
"continue": true,
"hookSpecificOutput": {
"permissionDecision": "ask"
},
"systemMessage": "⚠️ **CAUTION**: $reason\n\nThis is a destructive operation. Please confirm with the user before proceeding."
}
EOF
exit 0
fi
done
# =============================================================================
# CORE CLI SUGGESTIONS - Map raw commands to safer alternatives
# =============================================================================
# Check for suggestions - format: "pattern|core_command|reason|category"
suggestions=(
# === GO COMMANDS ===
"go build|core build|Handles cross-compilation, signing, checksums, and release packaging|go"
"go test|core go qa --only=test|Includes race detection, coverage reporting, and proper CI output|go"
"go test -race|core go qa --race|Runs tests with race detection and coverage|go"
"go test -cover|core go qa --coverage|Runs tests with coverage threshold enforcement|go"
"go fmt|core go qa --fix|Also runs vet and lint with auto-fix|go"
"gofmt|core go fmt|Uses project formatting configuration|go"
"go vet|core go qa quick|Runs fmt, vet, and lint together|go"
"golangci-lint|core go lint|Configured with project-specific linter rules|go"
"go mod tidy|core go mod tidy|Runs with verification|go"
"go mod download|core go mod tidy|Ensures consistent dependencies|go"
"go install|core go install|Installs to correct GOBIN location|go"
"go work sync|core go work sync|Handles workspace sync across modules|go"
"go generate|core go qa --fix|Runs generators as part of QA|go"
"staticcheck|core go lint|Included in golangci-lint configuration|go"
"govulncheck|core go qa full|Security scan included in full QA|go"
"gosec|core go qa full|Security scan included in full QA|go"
# === PHP/LARAVEL COMMANDS ===
"phpunit|core php test|Includes coverage and proper CI reporting|php"
"pest|core php test|Includes coverage and proper CI reporting|php"
"composer test|core php test|Includes coverage and proper CI reporting|php"
"php artisan test|core php test|Includes coverage and proper CI reporting|php"
"php-cs-fixer|core php fmt|Uses Laravel Pint with project config|php"
"pint|core php fmt|Runs with project configuration|php"
"./vendor/bin/pint|core php fmt|Runs with project configuration|php"
"phpstan|core php stan|Configured with project baseline|php"
"./vendor/bin/phpstan|core php stan|Configured with project baseline|php"
"psalm|core php psalm|Runs with project configuration|php"
"./vendor/bin/psalm|core php psalm|Runs with project configuration|php"
"rector|core php rector|Automated refactoring with project rules|php"
"composer audit|core php audit|Security audit with detailed reporting|php"
"php artisan serve|core php dev|Full dev environment with hot reload|php"
"php -S localhost|core php dev|Full dev environment with services|php"
"composer install|core php dev|Handles dependencies in dev environment|php"
"composer update|core php qa|Runs QA after dependency updates|php"
"php artisan migrate|core php dev|Run migrations in dev environment|php"
"infection|core php infection|Mutation testing with proper config|php"
# === GIT COMMANDS ===
"git push origin|core git push|Safe multi-repo push with checks|git"
"git push -u|core git push|Safe push with upstream tracking|git"
"git pull origin|core git pull|Safe multi-repo pull|git"
"git pull --rebase|core git pull|Safe pull with rebase handling|git"
"git commit -m|core git commit|Claude-assisted commit messages|git"
"git commit -am|core git commit|Claude-assisted commits with staging|git"
"git status|core git health|Shows health across all repos|git"
"git log --oneline|core git health|Shows status across all repos|git"
"git stash|Consider: core git commit|Commit WIP instead of stashing|git"
# === DOCKER COMMANDS ===
"docker build|core build|Handles multi-arch builds and registry push|docker"
"docker-compose up|core php dev|Managed dev environment|docker"
"docker compose up|core php dev|Managed dev environment|docker"
"docker run|core vm run|Use LinuxKit VMs for isolation|docker"
# === DEPLOYMENT COMMANDS ===
"ansible-playbook|core deploy ansible|Native Ansible without Python dependency|deploy"
"traefik|core deploy|Managed Traefik configuration|deploy"
"ssh|core vm exec|Execute in managed VM instead|deploy"
# === SECURITY COMMANDS ===
"npm audit|core security deps|Aggregated security across repos|security"
"yarn audit|core security deps|Aggregated security across repos|security"
"trivy|core security scan|Integrated vulnerability scanning|security"
"snyk|core security scan|Integrated vulnerability scanning|security"
"grype|core security scan|Integrated vulnerability scanning|security"
# === DOCUMENTATION ===
"godoc|core docs list|Lists documentation across repos|docs"
"pkgsite|core docs list|Managed documentation server|docs"
# === DEVELOPMENT WORKFLOW ===
"gh pr create|core ai task:pr|Creates PR with task reference|workflow"
"gh pr list|core qa review|Shows PRs needing review|workflow"
"gh issue list|core dev issues|Lists issues across all repos|workflow"
"gh run list|core dev ci|Shows CI status across repos|workflow"
"gh run watch|core qa watch|Watches CI after push|workflow"
# === FORGEJO ===
"curl.*localhost:4000/api|core forge|Managed Forgejo API interactions with auth|forgejo"
"curl.*forge.lthn.ai.*api|core forge|Managed Forgejo API interactions with auth|forgejo"
"curl.*forgejo.*api/v1/repos|core forge repos|Lists repos with filtering|forgejo"
"curl.*forgejo.*api/v1/orgs|core forge orgs|Lists organisations|forgejo"
# === PACKAGE MANAGEMENT ===
"git clone|core pkg install|Clones with proper workspace setup|packages"
"go get|core pkg install|Managed package installation|packages"
)
# Find first matching suggestion
for entry in "${suggestions[@]}"; do
pattern=$(echo "$entry" | cut -d'|' -f1)
core_cmd=$(echo "$entry" | cut -d'|' -f2)
reason=$(echo "$entry" | cut -d'|' -f3)
category=$(echo "$entry" | cut -d'|' -f4)
if [[ "$command" == *"$pattern"* ]]; then
log_intercept "SUGGESTED" "$command" "$core_cmd" "$reason"
cat << EOF
{
"continue": true,
"hookSpecificOutput": {
"permissionDecision": "allow"
},
"systemMessage": "💡 **Core CLI Alternative:**\n\nInstead of: \`$pattern\`\nUse: \`$core_cmd\`\n\n**Why:** $reason\n\nProceeding with original command, but consider using core CLI for better safety and reporting."
}
EOF
exit 0
fi
done
# =============================================================================
# LARGE-SCALE OPERATIONS - Extra caution for bulk changes
# =============================================================================
# Detect potentially large-scale destructive operations
if [[ "$command" =~ (rm|delete|remove|drop|truncate|wipe|clean|purge|reset|revert).*(--all|-a|-r|-rf|-fr|--force|-f|\*|\.\.\.|\*\*) ]]; then
log_intercept "BULK_OPERATION" "$command" "" "Detected bulk/recursive operation"
cat << EOF
{
"continue": true,
"hookSpecificOutput": {
"permissionDecision": "ask"
},
"systemMessage": "🔍 **Bulk Operation Detected**\n\nThis command appears to perform a bulk or recursive operation. Before proceeding:\n\n1. Verify the scope is correct\n2. Consider running with --dry-run first if available\n3. Confirm with the user this is intentional\n\nCommand: \`$command\`"
}
EOF
exit 0
fi
# Detect sed/awk operations on multiple files (potential for mass changes)
if [[ "$command" =~ (sed|awk|perl).+-i.*(\*|find|\$\(|xargs) ]]; then
log_intercept "MASS_EDIT" "$command" "" "Detected in-place edit on multiple files"
cat << EOF
{
"continue": true,
"hookSpecificOutput": {
"permissionDecision": "ask"
},
"systemMessage": "📝 **Mass File Edit Detected**\n\nThis command will edit multiple files in-place. Consider:\n\n1. Run without -i first to preview changes\n2. Ensure you have git backup of current state\n3. Verify the file pattern matches expected files\n\nCommand: \`$command\`"
}
EOF
exit 0
fi
# =============================================================================
# NO MATCH - Allow silently
# =============================================================================
echo '{"continue": true}'