From 499079849c3bf74cd9798de754a01a7c4e4108d5 Mon Sep 17 00:00:00 2001 From: William Zujkowski Date: Mon, 2 Feb 2026 17:26:58 -0500 Subject: [PATCH 1/3] enhance: Add Claude Code skills and hooks integration - Add /moltdown skill for quick VM workflow reference - Add setup-hooks.sh script for automated hook installation - Add hooks: shellcheck-on-write, session-context, validate-bash - Update CLAUDE.md with integration documentation - Update .gitignore to track .claude/ skills and hooks Hooks provide: - Automatic shellcheck linting on .sh file modifications - Project context injection at session start - Protection against destructive VM commands Co-Authored-By: Claude Opus 4.5 --- .claude/hooks/session-context.sh | 21 ++ .claude/hooks/shellcheck-on-write.sh | 31 +++ .claude/hooks/validate-bash.sh | 26 ++ .claude/settings.json | 38 +++ .claude/skills/moltdown/SKILL.md | 117 ++++++++ .gitignore | 5 +- CLAUDE.md | 71 ++++- setup-hooks.sh | 396 +++++++++++++++++++++++++++ 8 files changed, 702 insertions(+), 3 deletions(-) create mode 100755 .claude/hooks/session-context.sh create mode 100755 .claude/hooks/shellcheck-on-write.sh create mode 100755 .claude/hooks/validate-bash.sh create mode 100644 .claude/settings.json create mode 100644 .claude/skills/moltdown/SKILL.md create mode 100755 setup-hooks.sh diff --git a/.claude/hooks/session-context.sh b/.claude/hooks/session-context.sh new file mode 100755 index 0000000..9b4b5b0 --- /dev/null +++ b/.claude/hooks/session-context.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# SessionStart hook: Inject project context reminder +# Outputs text that Claude will see at session start + +set -euo pipefail + +INPUT=$(cat) +SESSION_TYPE=$(echo "$INPUT" | jq -r '.matcher_value // "unknown"') + +# Only inject on fresh startup, not resume/compact +if [[ "$SESSION_TYPE" == "startup" ]]; then + cat << 'CONTEXT' +moltdown project context: +- VM workflow toolkit for AI agents +- Use 'make lint' before commits +- Shell scripts must pass shellcheck -x +- Key commands: ./agent.sh, ./snapshot_manager.sh, ./clone_manager.sh +CONTEXT +fi + +exit 0 diff --git a/.claude/hooks/shellcheck-on-write.sh b/.claude/hooks/shellcheck-on-write.sh new file mode 100755 index 0000000..5aa5c80 --- /dev/null +++ b/.claude/hooks/shellcheck-on-write.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# PostToolUse hook: Run shellcheck on modified .sh files +# Exit 0 = proceed, Exit 2 = block (not used here, just advisory) + +set -euo pipefail + +# Read input from stdin +INPUT=$(cat) + +# Extract file path from tool input +FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') + +# Only check .sh files +if [[ -z "$FILE_PATH" ]] || [[ ! "$FILE_PATH" =~ \.sh$ ]]; then + exit 0 +fi + +# Check if file exists +if [[ ! -f "$FILE_PATH" ]]; then + exit 0 +fi + +# Run shellcheck if available +if command -v shellcheck &>/dev/null; then + if ! shellcheck -x "$FILE_PATH" 2>&1; then + # Output goes to Claude as feedback (not blocking) + echo "shellcheck found issues in $FILE_PATH" >&2 + fi +fi + +exit 0 diff --git a/.claude/hooks/validate-bash.sh b/.claude/hooks/validate-bash.sh new file mode 100755 index 0000000..b8848f3 --- /dev/null +++ b/.claude/hooks/validate-bash.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# PreToolUse hook: Block dangerous bash commands +# Exit 0 = allow, Exit 2 = block with reason + +set -euo pipefail + +INPUT=$(cat) +COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') + +# Patterns to block (destructive VM/disk operations) +DANGEROUS_PATTERNS=( + 'virsh.*destroy.*ubuntu2404-agent' # Don't destroy the main golden VM + 'virsh.*undefine.*ubuntu2404-agent' + 'rm.*\.qcow2' # Don't delete disk images + 'rm -rf /var/lib/libvirt' +) + +for pattern in "${DANGEROUS_PATTERNS[@]}"; do + if echo "$COMMAND" | grep -qE "$pattern"; then + echo "Blocked: This command could destroy the golden image or disk images." >&2 + echo "Use clone_manager.sh for disposable VMs instead." >&2 + exit 2 + fi +done + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..c6f09ab --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,38 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/shellcheck-on-write.sh", + "timeout": 30 + } + ] + } + ], + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-context.sh" + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-bash.sh" + } + ] + } + ] + } +} diff --git a/.claude/skills/moltdown/SKILL.md b/.claude/skills/moltdown/SKILL.md new file mode 100644 index 0000000..7149977 --- /dev/null +++ b/.claude/skills/moltdown/SKILL.md @@ -0,0 +1,117 @@ +--- +name: moltdown +description: | + moltdown VM workflow toolkit for AI agents. Use when working with VM creation, + snapshots, clones, bootstrapping, or agent sessions. Triggers on "VM", "snapshot", + "clone", "bootstrap", "golden image", "agent VM", "moltdown", "libvirt". +argument-hint: [command] +allowed-tools: Read, Bash, Grep, Glob +--- + +# moltdown - Golden Image VM Workflow + + + +## Quick Reference + +```bash +# One-command agent VM (start here!) +./agent.sh # Create + connect to agent VM + +# Setup (first time only) +./setup_cloud.sh # Default: 16GB RAM, 4 vCPUs +./setup_cloud.sh --memory 8192 --vcpus 4 # Lightweight + +# Snapshot workflow +./snapshot_manager.sh list # List snapshots +./snapshot_manager.sh pre-run # Before agent work +./snapshot_manager.sh post-run # Revert to dev-ready +./snapshot_manager.sh golden # Create golden image + +# Clone workflow (parallel agents) +./clone_manager.sh create --linked # Instant clone +./clone_manager.sh create --linked --memory 8192 # 8GB clone +./clone_manager.sh list # List all +./clone_manager.sh cleanup # Delete all + +# Inside VM +vm-health-check # Quick health status +vm-health-check --watch # Continuous monitoring +run-claude-limited # Run Claude with 12GB limit +agent-session # Persistent tmux session +``` + +## Core Concepts + +### Golden Image Workflow +1. **os-clean**: Fresh Ubuntu + updates +2. **dev-ready**: Bootstrap complete (tools installed, auth configured) +3. **pre-run**: Before each agent run (timestamped) +4. **post-run**: Revert to dev-ready after work + +### Memory Planning +- Claude CLI can leak to 13GB+ +- 64GB host: 2-4 clones @ 12-16GB each +- Always use `run-claude-limited` inside VMs + +## Script Reference + +| Script | Purpose | +|--------|---------| +| `agent.sh` | One-command agent VM creation | +| `setup_cloud.sh` | Full VM setup (cloud images) | +| `snapshot_manager.sh` | Manage libvirt snapshots | +| `clone_manager.sh` | Manage VM clones | +| `sync-ai-auth.sh` | Sync AI CLI auth to VMs | +| `update-golden.sh` | Update golden image | +| `guest/bootstrap_agent_vm.sh` | Run inside VM to configure it | + +## Common Workflows + +### Start Fresh Agent Session +```bash +./agent.sh +# Inside VM: +agent-session +``` + +### Run Multiple Agents in Parallel +```bash +./clone_manager.sh create ubuntu2404-agent --linked --memory 8192 +./clone_manager.sh create ubuntu2404-agent --linked --memory 8192 +./clone_manager.sh start moltdown-clone-* +``` + +### Update Golden Image +```bash +./update-golden.sh # Full update +./update-golden.sh --quick # CLIs only +./update-golden.sh --auth-only # Re-sync auth +``` + +## Shell Standards + +All scripts follow: +- `set -euo pipefail` strict mode +- `readonly` for constants +- `local` for function variables +- Logging: `log_info()`, `log_warn()`, `log_error()`, `log_phase()` +- All scripts must pass `shellcheck -x` + +## Quality Gates + +Before committing: +- [ ] `make lint` passes +- [ ] Scripts are executable (`chmod +x`) +- [ ] No hardcoded paths that should be configurable +- [ ] `--help` updated if CLI changed + +## Related Files + +- [README.md](../../../README.md) - Full documentation +- [RESOURCES.md](../../../RESOURCES.md) - Memory planning +- [CLAUDE.md](../../../CLAUDE.md) - Development guidelines diff --git a/.gitignore b/.gitignore index bf1eee4..4b79b89 100644 --- a/.gitignore +++ b/.gitignore @@ -23,8 +23,9 @@ Thumbs.db *.swp *.swo -# Claude Code local settings -.claude/ +# Claude Code local settings (keep skills and hooks, ignore local settings) +.claude/settings.local.json +.claude/*.bak # MCP server config (local) .mcp.json diff --git a/CLAUDE.md b/CLAUDE.md index 9448acd..cfdb370 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,6 +60,11 @@ systemctl status claude-watchdog # Check watchdog service make lint # Run shellcheck + yamllint shellcheck -x *.sh guest/*.sh # Manual shellcheck +# Claude Code Integration +./setup-hooks.sh # Install Claude Code hooks (shellcheck, context) +./setup-hooks.sh --dry-run # Preview hook changes +./setup-hooks.sh --remove # Remove hooks + # GitHub CLI gh issue create # Create issue gh issue list # List issues @@ -502,6 +507,70 @@ When working with this codebase, ensure: --- +## Claude Code Integration + +### Skills + +The `/moltdown` skill provides quick reference for VM workflows: + +```bash +# In Claude Code, type: +/moltdown # Show quick reference +/moltdown snapshot # Help with snapshot commands +``` + +Skill location: `.claude/skills/moltdown/SKILL.md` + +### Hooks + +Configure Claude Code hooks for automatic shell script validation: + +```bash +# Install hooks +./setup-hooks.sh + +# Preview without changes +./setup-hooks.sh --dry-run + +# Remove hooks +./setup-hooks.sh --remove +``` + +**Installed hooks:** + +| Hook | Event | Purpose | +|------|-------|---------| +| `shellcheck-on-write.sh` | PostToolUse[Write\|Edit] | Lint .sh files after modification | +| `session-context.sh` | SessionStart[startup] | Inject project context | +| `validate-bash.sh` | PreToolUse[Bash] | Block dangerous VM commands | + +Hook scripts: `.claude/hooks/` +Hook config: `.claude/settings.json` + +### Manual Hook Configuration + +If you prefer manual setup, add to `.claude/settings.json`: + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/shellcheck-on-write.sh" + } + ] + } + ] + } +} +``` + +--- + ## Future Improvements / TODOs - [ ] Support for other distros (Fedora, Debian) @@ -512,4 +581,4 @@ When working with this codebase, ensure: --- -_Last updated: 2026-02-02 (ET)_ +_Last updated: 2026-02-02 (ET) - Added Claude Code hooks and skills_ diff --git a/setup-hooks.sh b/setup-hooks.sh new file mode 100755 index 0000000..8f66d92 --- /dev/null +++ b/setup-hooks.sh @@ -0,0 +1,396 @@ +#!/usr/bin/env bash +#=============================================================================== +# setup-hooks.sh - Configure Claude Code hooks for moltdown project +#=============================================================================== +# Part of moltdown - https://github.com/williamzujkowski/moltdown +# +# Purpose: Set up Claude Code hooks for shell script validation, lint checking, +# and project context injection. Idempotent - safe to run multiple times. +# +# Usage: ./setup-hooks.sh [--dry-run] [--force] +# +# Options: +# --dry-run Show what would be done without making changes +# --force Overwrite existing hook scripts +# --remove Remove all moltdown hooks +# +# License: MIT +#=============================================================================== + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SCRIPT_DIR +readonly CLAUDE_DIR="$SCRIPT_DIR/.claude" +readonly HOOKS_DIR="$CLAUDE_DIR/hooks" +readonly SETTINGS_FILE="$CLAUDE_DIR/settings.json" + +# Flags +DRY_RUN=false +FORCE=false +REMOVE=false + +#------------------------------------------------------------------------------- +# Logging +#------------------------------------------------------------------------------- +log_info() { echo "[INFO] $*"; } +log_warn() { echo "[WARN] $*"; } +log_error() { echo "[ERROR] $*" >&2; } +log_dry() { echo "[DRY] $*"; } + +#------------------------------------------------------------------------------- +# Usage +#------------------------------------------------------------------------------- +usage() { + cat << 'EOF' +Usage: ./setup-hooks.sh [OPTIONS] + +Configure Claude Code hooks for moltdown project. + +Options: + --dry-run Show what would be done without making changes + --force Overwrite existing hook scripts (default: skip existing) + --remove Remove all moltdown hooks + -h, --help Show this help message + +Hooks installed: + 1. PostToolUse[Write|Edit] - Run shellcheck on modified .sh files + 2. SessionStart[startup] - Inject project context reminder + 3. PreToolUse[Bash] - Validate dangerous commands + +Examples: + ./setup-hooks.sh # Install hooks + ./setup-hooks.sh --dry-run # Preview changes + ./setup-hooks.sh --remove # Remove hooks + ./setup-hooks.sh --force # Reinstall all hooks +EOF +} + +#------------------------------------------------------------------------------- +# Parse Arguments +#------------------------------------------------------------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) + DRY_RUN=true + shift + ;; + --force) + FORCE=true + shift + ;; + --remove) + REMOVE=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + log_error "Unknown option: $1" + usage + exit 1 + ;; + esac +done + +#------------------------------------------------------------------------------- +# Remove hooks +#------------------------------------------------------------------------------- +remove_hooks() { + log_info "Removing moltdown hooks..." + + if [[ -d "$HOOKS_DIR" ]]; then + if $DRY_RUN; then + log_dry "Would remove: $HOOKS_DIR" + else + rm -rf "$HOOKS_DIR" + log_info "Removed: $HOOKS_DIR" + fi + fi + + if [[ -f "$SETTINGS_FILE" ]]; then + if $DRY_RUN; then + log_dry "Would remove hooks from: $SETTINGS_FILE" + else + # Remove hooks key from settings if it exists + if command -v jq &>/dev/null; then + local tmp_file + tmp_file=$(mktemp) + jq 'del(.hooks)' "$SETTINGS_FILE" > "$tmp_file" + mv "$tmp_file" "$SETTINGS_FILE" + log_info "Removed hooks from: $SETTINGS_FILE" + else + log_warn "jq not installed - manually edit $SETTINGS_FILE to remove hooks" + fi + fi + fi + + log_info "Hooks removed successfully" +} + +#------------------------------------------------------------------------------- +# Create hook scripts +#------------------------------------------------------------------------------- +create_hook_scripts() { + log_info "Creating hook scripts..." + + if $DRY_RUN; then + log_dry "Would create: $HOOKS_DIR" + else + mkdir -p "$HOOKS_DIR" + fi + + # Hook 1: Shellcheck validator for modified shell scripts + local shellcheck_hook="$HOOKS_DIR/shellcheck-on-write.sh" + if [[ -f "$shellcheck_hook" ]] && ! $FORCE; then + log_info "Skipping existing: $shellcheck_hook" + else + if $DRY_RUN; then + log_dry "Would create: $shellcheck_hook" + else + cat > "$shellcheck_hook" << 'HOOK_EOF' +#!/usr/bin/env bash +# PostToolUse hook: Run shellcheck on modified .sh files +# Exit 0 = proceed, Exit 2 = block (not used here, just advisory) + +set -euo pipefail + +# Read input from stdin +INPUT=$(cat) + +# Extract file path from tool input +FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') + +# Only check .sh files +if [[ -z "$FILE_PATH" ]] || [[ ! "$FILE_PATH" =~ \.sh$ ]]; then + exit 0 +fi + +# Check if file exists +if [[ ! -f "$FILE_PATH" ]]; then + exit 0 +fi + +# Run shellcheck if available +if command -v shellcheck &>/dev/null; then + if ! shellcheck -x "$FILE_PATH" 2>&1; then + # Output goes to Claude as feedback (not blocking) + echo "shellcheck found issues in $FILE_PATH" >&2 + fi +fi + +exit 0 +HOOK_EOF + chmod +x "$shellcheck_hook" + log_info "Created: $shellcheck_hook" + fi + fi + + # Hook 2: Session context injection + local session_hook="$HOOKS_DIR/session-context.sh" + if [[ -f "$session_hook" ]] && ! $FORCE; then + log_info "Skipping existing: $session_hook" + else + if $DRY_RUN; then + log_dry "Would create: $session_hook" + else + cat > "$session_hook" << 'HOOK_EOF' +#!/usr/bin/env bash +# SessionStart hook: Inject project context reminder +# Outputs text that Claude will see at session start + +set -euo pipefail + +INPUT=$(cat) +SESSION_TYPE=$(echo "$INPUT" | jq -r '.matcher_value // "unknown"') + +# Only inject on fresh startup, not resume/compact +if [[ "$SESSION_TYPE" == "startup" ]]; then + cat << 'CONTEXT' +moltdown project context: +- VM workflow toolkit for AI agents +- Use 'make lint' before commits +- Shell scripts must pass shellcheck -x +- Key commands: ./agent.sh, ./snapshot_manager.sh, ./clone_manager.sh +CONTEXT +fi + +exit 0 +HOOK_EOF + chmod +x "$session_hook" + log_info "Created: $session_hook" + fi + fi + + # Hook 3: Dangerous command validator + local bash_hook="$HOOKS_DIR/validate-bash.sh" + if [[ -f "$bash_hook" ]] && ! $FORCE; then + log_info "Skipping existing: $bash_hook" + else + if $DRY_RUN; then + log_dry "Would create: $bash_hook" + else + cat > "$bash_hook" << 'HOOK_EOF' +#!/usr/bin/env bash +# PreToolUse hook: Block dangerous bash commands +# Exit 0 = allow, Exit 2 = block with reason + +set -euo pipefail + +INPUT=$(cat) +COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') + +# Patterns to block (destructive VM/disk operations) +DANGEROUS_PATTERNS=( + 'virsh.*destroy.*ubuntu2404-agent' # Don't destroy the main golden VM + 'virsh.*undefine.*ubuntu2404-agent' + 'rm.*\.qcow2' # Don't delete disk images + 'rm -rf /var/lib/libvirt' +) + +for pattern in "${DANGEROUS_PATTERNS[@]}"; do + if echo "$COMMAND" | grep -qE "$pattern"; then + echo "Blocked: This command could destroy the golden image or disk images." >&2 + echo "Use clone_manager.sh for disposable VMs instead." >&2 + exit 2 + fi +done + +exit 0 +HOOK_EOF + chmod +x "$bash_hook" + log_info "Created: $bash_hook" + fi + fi +} + +#------------------------------------------------------------------------------- +# Create/update settings.json with hooks configuration +#------------------------------------------------------------------------------- +update_settings() { + log_info "Updating settings.json with hook configuration..." + + # Define the hooks configuration + local hooks_json + hooks_json=$(cat << 'JSON_EOF' +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/shellcheck-on-write.sh", + "timeout": 30 + } + ] + } + ], + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-context.sh" + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-bash.sh" + } + ] + } + ] + } +} +JSON_EOF +) + + if $DRY_RUN; then + log_dry "Would update: $SETTINGS_FILE" + log_dry "Hooks configuration:" + echo "$hooks_json" | head -20 + return 0 + fi + + # Check for jq + if ! command -v jq &>/dev/null; then + log_error "jq is required but not installed. Install with: sudo apt install jq" + log_info "Manual configuration:" + echo "$hooks_json" + exit 1 + fi + + # Create or merge with existing settings + if [[ -f "$SETTINGS_FILE" ]]; then + # Backup existing + cp "$SETTINGS_FILE" "$SETTINGS_FILE.bak" + log_info "Backed up existing settings to: $SETTINGS_FILE.bak" + + # Merge hooks into existing settings + local merged + merged=$(jq -s '.[0] * .[1]' "$SETTINGS_FILE" <(echo "$hooks_json")) + echo "$merged" > "$SETTINGS_FILE" + log_info "Merged hooks into existing settings" + else + # Create new settings file + mkdir -p "$(dirname "$SETTINGS_FILE")" + echo "$hooks_json" > "$SETTINGS_FILE" + log_info "Created new settings file: $SETTINGS_FILE" + fi +} + +#------------------------------------------------------------------------------- +# Main +#------------------------------------------------------------------------------- +main() { + echo "╔═══════════════════════════════════════════════════════════════════╗" + echo "║ moltdown - Claude Code Hooks Setup ║" + echo "╚═══════════════════════════════════════════════════════════════════╝" + echo "" + + if $REMOVE; then + remove_hooks + exit 0 + fi + + # Prerequisites check + if ! command -v jq &>/dev/null; then + log_warn "jq not installed - some features may not work" + log_info "Install with: sudo apt install jq" + fi + + if ! command -v shellcheck &>/dev/null; then + log_warn "shellcheck not installed - lint hooks won't work" + log_info "Install with: sudo apt install shellcheck" + fi + + create_hook_scripts + update_settings + + echo "" + log_info "Setup complete!" + echo "" + echo "Hooks installed:" + echo " 1. PostToolUse[Write|Edit] - shellcheck on .sh files" + echo " 2. SessionStart[startup] - project context injection" + echo " 3. PreToolUse[Bash] - dangerous command blocking" + echo "" + echo "To activate hooks, restart Claude Code or run /hooks to reload." + echo "" + if $DRY_RUN; then + echo "NOTE: This was a dry run. No changes were made." + fi +} + +main "$@" From 2f9f40ef11e64962527d96f30eb13b85b04f4cc5 Mon Sep 17 00:00:00 2001 From: William Zujkowski Date: Mon, 2 Feb 2026 17:40:08 -0500 Subject: [PATCH 2/3] ci: trigger build From 6cc4d1a14c6f34af0dc1d3396c8538732ca192e3 Mon Sep 17 00:00:00 2001 From: William Zujkowski Date: Mon, 2 Feb 2026 17:59:44 -0500 Subject: [PATCH 3/3] ci: retry