Module 7: Remote Control and CLAUDE.md for Teams

5. Project — Team Setup with a Standard CLAUDE.md and Remote Control

5. Project — Team Setup with a Standard CLAUDE.md and Remote Control

Project Description

You've learned each piece separately: remote control for remote supervision, approval flows for sensitive operations, CLAUDE.md as a team constitution, enforcement with hooks, and the merge hierarchy. Now you integrate everything into a production-ready team setup.

In this project you build the complete operational setup for a 3-person team working on an e-commerce API. You design the shared CLAUDE.md, configure remote control, define approval flows for destructive operations, create enforcement hooks, and document the onboarding process so any new member can adopt the standard in 5 minutes.

The result is a repository that any developer can clone and start working on with Claude Code following the same rules, with no alignment meetings, no 20-page documents, no "how did you configure your Claude?" The CLAUDE.md is the constitution, the hooks are the police, and remote control is the monitoring center.

This project closes Module 7 and directly prepares Module 8 (Capstone Project), where this setup is the operational base on which you'll build the complete multi-agent system.


Project Objective

Build a complete team setup with CLAUDE.md, remote control, approval flows, enforcement hooks, and an onboarding process, verify that everything works in an integrated way, and document the process to replicate it in any project.

By completing this project:

  • ✅ You'll have a production-ready team CLAUDE.md with conventions, architecture, prohibitions, testing, and security
  • ✅ A settings.json with enforcement hooks that validate compliance automatically
  • ✅ Approval flow scripts for destructive operations (force push, database changes, publish)
  • ✅ Remote control configuration for monitoring and approval from other devices
  • ✅ CLAUDE.md in subdirectories for module-specific rules
  • ✅ An onboarding checklist a new member can follow in 5 minutes
  • ✅ Everything versioned and ready to commit

Estimated duration: 1-1.5 hours (CLAUDE.md: 20 min + hooks: 20 min + approval flows: 15 min + remote control: 10 min + onboarding: 10 min + verification: 15 min).


Technical Specifications

Project context

The team works on ShopFlow API — an e-commerce REST API with:

  • Stack: Python 3.12 + FastAPI + PostgreSQL + Redis
  • Team: 3 developers (lead, backend, frontend-api)
  • Repo: Monorepo with app/, tests/, scripts/, docs/
  • CI: GitHub Actions
  • Deploy: Docker + AWS

Final project structure

When you finish, your project will have these files:

shopflow-api/
├── CLAUDE.md                           ← Team constitution
├── .claude/
│   ├── settings.json                   ← Hooks + remote control config
│   ├── agents/                         ← Team subagents
│   │   ├── backend-specialist.md
│   │   └── review-agent.md
│   ├── approvals/                      ← Approval requests directory
│   └── logs/                           ← Enforcement and approval logs
│       └── approval-audit.csv
├── app/
│   ├── CLAUDE.md                       ← Backend-specific rules
│   ├── api/
│   │   └── CLAUDE.md                   ← Endpoint-specific rules
│   ├── models/
│   ├── schemas/
│   ├── services/
│   └── core/
├── tests/
│   └── CLAUDE.md                       ← Testing-specific rules
├── scripts/
│   ├── hooks/
│   │   ├── enforce-conventions.sh      ← PostToolUse: validate conventions
│   │   ├── enforce-architecture.sh     ← PostToolUse: validate layers
│   │   ├── enforce-security.sh         ← PostToolUse: no secrets in code
│   │   ├── approval-gate.sh            ← PermissionRequest: approval flow
│   │   ├── block-destructive.sh        ← PreToolUse: block dangerous operations
│   │   └── session-setup.sh            ← SessionStart: automatic setup
│   ├── verify-setup.sh                 ← Onboarding verification script
│   └── monitor-remote.py              ← Remote monitoring script
├── docs/
│   └── ONBOARDING.md                   ← Onboarding checklist
└── .github/
    └── CONTRIBUTING.md                 ← Link to CLAUDE.md and process

Step 1: Create the Directory Structure

mkdir -p .claude/agents
mkdir -p .claude/approvals
mkdir -p .claude/logs
mkdir -p scripts/hooks
mkdir -p app/api app/models app/schemas app/services app/core
mkdir -p tests
mkdir -p docs
mkdir -p .github

Step 2: Main CLAUDE.md — The Team Constitution

Create CLAUDE.md in the project root:

# CLAUDE.md — ShopFlow API

## Context
ShopFlow is an e-commerce REST API. Stack: Python 3.12, FastAPI, PostgreSQL 16, Redis 7, SQLAlchemy 2.0 async. Team of 3 developers. Monorepo with app/, tests/, scripts/.

## Code Conventions

### Python
- Type hints required on ALL functions (parameters + return)
- Docstrings in Google format on public functions
- Descriptive variables: user_repository (NOT ur, NOT usrRepo)
- Functions of at most 30 lines
- f-strings for interpolation
- pathlib instead of os.path
- async/await for all I/O operations

### Naming
- Files: snake_case.py
- Classes: PascalCase
- Functions/variables: snake_case
- Constants: UPPER_SNAKE_CASE
- Endpoint URLs: kebab-case (/api/v1/order-items)

### Imports (strict order)
1. stdlib (os, sys, typing, datetime, pathlib)
2. third-party (fastapi, sqlalchemy, pydantic, redis)
3. local (app.models, app.services, app.schemas)
Separated by a blank line between groups.

## Architecture

### Layers (REQUIRED)
- Router → Service → Repository → DB
- NEVER Router → DB directly
- NEVER Repository with business logic
- NEVER circular imports between layers

### Patterns
- Repository Pattern for data access
- Dependency Injection via FastAPI Depends()
- Pydantic v2 for request/response schemas
- Factory pattern for tests

## Forbidden Patterns
- NO typing.Any without a "# justified: [reason]" comment
- NO print() — use the structlog logger
- NO raw SQL queries — always SQLAlchemy ORM
- DO NOT hardcode URLs, ports, or credentials
- NO datetime.now() — use datetime.now(UTC)
- DO NOT import from tests/ in production code
- DO NOT modify alembic/versions/ manually
- NO console.log in any file

## Testing
- Framework: pytest + httpx.AsyncClient + pytest-asyncio
- Minimum coverage: 80%
- Every endpoint: 1 happy path test + 1 error test
- Every service function: unit test
- Factories with factory-boy (no hardcoded data)
- Independent tests (no order dependency)

## Git
- Conventional Commits: type(scope): description
- Types: feat, fix, refactor, test, docs, chore, ci
- Messages in English, imperative, no final period
- Maximum 72 characters on the first line
- NEVER force push to main or develop

## Security
- NEVER commit .env, API keys, tokens, passwords
- bcrypt for password hashing
- JWT secrets at least 256 bits
- Rate limiting on public endpoints
- Pydantic validation on ALL endpoints
- Don't expose stack traces in error responses

Step 3: Subdirectory CLAUDE.md Files

app/CLAUDE.md

## Backend Application Rules
- All async functions use `async def`
- Custom exceptions in app/core/exceptions.py
- Centralized configuration in app/core/config.py via pydantic-settings
- Logger configured in app/core/logging.py — import from there
- Every new model requires a migration via alembic

app/api/CLAUDE.md

## API Endpoint Rules
- Each router in its own file: app/api/users.py, app/api/orders.py
- Endpoints return a Pydantic ResponseModel (never a raw dict)
- Error responses via app.core.exceptions (HTTPException only in the router)
- OpenAPI documentation with description and response_model
- Version prefix: /api/v1/
- List endpoints: GET, Create: POST, Update: PUT/PATCH, Delete: DELETE

tests/CLAUDE.md

## Testing Rules
- File name: test_[module].py
- Test name: test_[what]_[condition]_[result] (test_create_user_duplicate_email_returns_409)
- Shared fixtures in conftest.py
- Test database: use the db_session fixture with automatic rollback
- Don't use sleep() in tests — use asyncio mocks
- Each test creates its own data (don't depend on seed data)

Step 4: settings.json — Hooks and Remote Control

Create .claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/hooks/session-setup.sh"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/hooks/block-destructive.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/hooks/enforce-conventions.sh"
          },
          {
            "type": "command",
            "command": "./scripts/hooks/enforce-architecture.sh"
          },
          {
            "type": "command",
            "command": "./scripts/hooks/enforce-security.sh"
          }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/hooks/approval-gate.sh"
          }
        ]
      }
    ]
  },
  "remote": {
    "enabled": true,
    "require_auth": true,
    "max_connections": 3,
    "allowed_operations": ["monitor", "approve", "reject"],
    "log_remote_actions": true,
    "auto_disconnect_idle_minutes": 30
  }
}

Step 5: SessionStart Hook — Automatic Setup

Create scripts/hooks/session-setup.sh:

#!/bin/bash

echo "=== ShopFlow API — Session Setup ==="

LOG_DIR=".claude/logs"
mkdir -p "$LOG_DIR" ".claude/approvals"

if [ -f "$LOG_DIR/enforcement.log" ]; then
    mv "$LOG_DIR/enforcement.log" "$LOG_DIR/enforcement-$(date +%Y%m%d-%H%M%S).log.bak"
fi
touch "$LOG_DIR/enforcement.log"

if ! git rev-parse --git-dir > /dev/null 2>&1; then
    echo "ERROR: Not a git repository"
    exit 1
fi

BRANCH=$(git branch --show-current 2>/dev/null)
echo "Branch: $BRANCH"

DIRTY=$(git status --porcelain | wc -l | tr -d ' ')
if [ "$DIRTY" -gt 20 ]; then
    echo "WARNING: $DIRTY uncommitted files — consider committing first"
    exit 1
fi

if [ -f "requirements.txt" ]; then
    if [ -d ".venv" ]; then
        source .venv/bin/activate 2>/dev/null
    fi
fi

TOOLS_OK=true
for tool in ruff mypy jq; do
    if ! command -v $tool &> /dev/null; then
        echo "WARNING: $tool not found"
        TOOLS_OK=false
    fi
done

if [ "$TOOLS_OK" = true ]; then
    echo "Tools: ruff, mypy, jq ✓"
fi

find .claude/approvals/ -name "*.json" -mmin +60 -delete 2>/dev/null

echo "Session ID: $(date +%Y%m%d-%H%M%S)"
echo "=== SETUP COMPLETE ==="
exit 0
chmod +x scripts/hooks/session-setup.sh

Step 6: PreToolUse Hook — Block Destructive Operations

Create scripts/hooks/block-destructive.sh:

#!/bin/bash

INPUT=$(cat -)

TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

if [ "$TOOL_NAME" != "Bash" ] || [ -z "$COMMAND" ]; then
    exit 0
fi

BLOCKED=(
    "rm -rf /"
    "rm -rf ~"
    "rm -rf \."
    "DROP DATABASE"
    "DROP TABLE"
    "TRUNCATE TABLE"
    "mkfs"
    "dd if="
    ":(){:|:&};:"
    "chmod -R 777 /"
    "npm publish"
    "pip upload"
    "twine upload"
)

for pattern in "${BLOCKED[@]}"; do
    if echo "$COMMAND" | grep -qi "$pattern"; then
        echo "BLOCKED: Destructive operation detected"
        echo "Pattern: $pattern"
        echo "Command: $COMMAND"
        TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
        echo "$TIMESTAMP | BLOCKED | $pattern | $COMMAND" >> .claude/logs/enforcement.log
        exit 2
    fi
done

NEEDS_APPROVAL=(
    "git push --force"
    "git push.*-f "
    "git reset --hard"
    "alembic downgrade"
    "docker.*production"
    "kubectl.*--force"
)

for pattern in "${NEEDS_APPROVAL[@]}"; do
    if echo "$COMMAND" | grep -qiE "$pattern"; then
        echo "REQUIRES APPROVAL: $COMMAND"
        echo "This operation needs manual approval via remote control."
        TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
        echo "$TIMESTAMP | NEEDS_APPROVAL | $pattern | $COMMAND" >> .claude/logs/enforcement.log
        exit 1
    fi
done

exit 0
chmod +x scripts/hooks/block-destructive.sh

Step 7: PostToolUse Hook — Enforce Conventions

Create scripts/hooks/enforce-conventions.sh:

#!/bin/bash

INPUT=$(cat -)

FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')

if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
    exit 0
fi

TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
VIOLATIONS=0

if [[ "$FILE" == *.py ]]; then
    BASENAME=$(basename "$FILE")
    if ! echo "$BASENAME" | grep -qE "^[a-z][a-z0-9_]*\.py$"; then
        echo "CONVENTION VIOLATION: Python filename must be snake_case"
        echo "Got: $BASENAME"
        VIOLATIONS=$((VIOLATIONS + 1))
    fi

    if grep -n "^import\|^from" "$FILE" | grep -v "^#" > /dev/null 2>&1; then
        if command -v ruff &> /dev/null; then
            IMPORT_CHECK=$(ruff check "$FILE" --select I --no-fix 2>&1)
            if [ $? -ne 0 ]; then
                echo "CONVENTION VIOLATION: Import order incorrect in $FILE"
                echo "Expected: stdlib > third-party > local"
                echo "$IMPORT_CHECK" | head -3
                VIOLATIONS=$((VIOLATIONS + 1))
            fi
        fi
    fi

    if grep -n "print(" "$FILE" | grep -v "^#\|# noqa\|# justified" > /dev/null 2>&1; then
        PRINT_LINES=$(grep -n "print(" "$FILE" | grep -v "^#\|# noqa\|# justified" | head -3)
        echo "CONVENTION VIOLATION: print() found in $FILE"
        echo "Use structlog logger instead:"
        echo "$PRINT_LINES"
        VIOLATIONS=$((VIOLATIONS + 1))
    fi

    if grep -n "datetime\.now()" "$FILE" | grep -v "now(UTC)\|now(timezone.utc)" > /dev/null 2>&1; then
        echo "CONVENTION VIOLATION: datetime.now() without UTC in $FILE"
        echo "Use: datetime.now(UTC)"
        VIOLATIONS=$((VIOLATIONS + 1))
    fi

    if command -v ruff &> /dev/null; then
        LINT_RESULT=$(ruff check "$FILE" --no-fix 2>&1)
        if [ $? -ne 0 ]; then
            echo "LINT ERRORS in $FILE:"
            echo "$LINT_RESULT" | head -5
            VIOLATIONS=$((VIOLATIONS + 1))
        fi
    fi
fi

if [ $VIOLATIONS -gt 0 ]; then
    echo "$TIMESTAMP | VIOLATIONS: $VIOLATIONS | $FILE" >> .claude/logs/enforcement.log
    exit 1
fi

echo "$TIMESTAMP | PASS | $FILE" >> .claude/logs/enforcement.log
exit 0
chmod +x scripts/hooks/enforce-conventions.sh

Step 8: PostToolUse Hook — Enforce Architecture

Create scripts/hooks/enforce-architecture.sh:

#!/bin/bash

INPUT=$(cat -)

FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')

if [ -z "$FILE" ] || [ ! -f "$FILE" ] || [[ "$FILE" != *.py ]]; then
    exit 0
fi

if echo "$FILE" | grep -q "^app/api/"; then
    if grep -n "from app\.models\|import.*Session\|from sqlalchemy" "$FILE" | grep -v "from app\.schemas\|from app\.services\|from app\.core" > /dev/null 2>&1; then
        DIRECT_DB=$(grep -n "Session\|engine\|select(\|insert(\|update(\|delete(" "$FILE" | grep -v "^#\|# allowed" | head -3)
        if [ -n "$DIRECT_DB" ]; then
            echo "ARCHITECTURE VIOLATION: Router accessing DB directly"
            echo "File: $FILE"
            echo "Rule: Router → Service → Repository → DB"
            echo "Found:"
            echo "$DIRECT_DB"
            exit 1
        fi
    fi
fi

if echo "$FILE" | grep -q "^app/repositories/"; then
    if grep -n "raise HTTPException\|from fastapi" "$FILE" | grep -v "^#" > /dev/null 2>&1; then
        echo "ARCHITECTURE VIOLATION: Repository contains HTTP logic"
        echo "File: $FILE"
        echo "Rule: Repositories handle data access only, not HTTP responses"
        exit 1
    fi
fi

if echo "$FILE" | grep -q "^app/" && [[ "$FILE" != *"test"* ]]; then
    if grep -n "from tests\.\|import tests\." "$FILE" > /dev/null 2>&1; then
        echo "ARCHITECTURE VIOLATION: Production code imports from tests/"
        echo "File: $FILE"
        exit 1
    fi
fi

exit 0
chmod +x scripts/hooks/enforce-architecture.sh

Step 9: PostToolUse Hook — Enforce Security

Create scripts/hooks/enforce-security.sh:

#!/bin/bash

INPUT=$(cat -)

FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')

if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
    exit 0
fi

BASENAME=$(basename "$FILE")
if echo "$BASENAME" | grep -qE "^\.env"; then
    echo "SECURITY VIOLATION: .env files must not be created or modified by agents"
    echo "File: $FILE"
    exit 2
fi

if [[ "$FILE" == *.py ]] || [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.js ]]; then
    SECRET_PATTERNS=(
        "password\s*=\s*[\"'][^\"']+[\"']"
        "api_key\s*=\s*[\"'][^\"']+[\"']"
        "secret\s*=\s*[\"'][^\"']+[\"']"
        "token\s*=\s*[\"'][A-Za-z0-9+/=_-]{20,}[\"']"
        "AWS_SECRET_ACCESS_KEY\s*=\s*[\"']"
        "PRIVATE_KEY\s*=\s*[\"']"
    )

    for pattern in "${SECRET_PATTERNS[@]}"; do
        if grep -inE "$pattern" "$FILE" | grep -v "os\.environ\|os\.getenv\|config\.\|settings\.\|\.env\|# example\|# test\|_PLACEHOLDER\|changeme\|xxx" > /dev/null 2>&1; then
            FOUND=$(grep -inE "$pattern" "$FILE" | grep -v "os\.environ\|os\.getenv\|config\.\|settings\.\|\.env\|# example\|# test\|_PLACEHOLDER\|changeme\|xxx" | head -2)
            echo "SECURITY VIOLATION: Potential hardcoded secret in $FILE"
            echo "Found:"
            echo "$FOUND"
            echo ""
            echo "Use environment variables or app.core.config instead."
            TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
            echo "$TIMESTAMP | SECURITY | hardcoded_secret | $FILE" >> .claude/logs/enforcement.log
            exit 1
        fi
    done
fi

exit 0
chmod +x scripts/hooks/enforce-security.sh

Step 10: PermissionRequest Hook — Approval Gate

Create scripts/hooks/approval-gate.sh:

#!/bin/bash

INPUT=$(cat -)

TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')

AUTO_APPROVE=(
    "Read"
    "Grep"
    "Glob"
)

for tool in "${AUTO_APPROVE[@]}"; do
    if [ "$TOOL_NAME" = "$tool" ]; then
        exit 0
    fi
done

if [ "$TOOL_NAME" = "Write" ] || [ "$TOOL_NAME" = "Edit" ]; then
    if echo "$FILE_PATH" | grep -qE "^(app/|tests/|docs/|scripts/)"; then
        exit 0
    fi
fi

if [ "$TOOL_NAME" = "Bash" ]; then
    if echo "$COMMAND" | grep -qE "^(ls|cat|echo|pwd|which|python -m pytest|ruff|mypy|git status|git log|git diff|git branch)"; then
        exit 0
    fi
fi

AUDIT_LOG=".claude/logs/approval-audit.csv"
if [ ! -f "$AUDIT_LOG" ]; then
    echo "timestamp,action,tool,detail" > "$AUDIT_LOG"
fi

APPROVAL_DIR=".claude/approvals"
mkdir -p "$APPROVAL_DIR"
APPROVAL_ID="approval-$(date +%s)-$$"

cat > "$APPROVAL_DIR/$APPROVAL_ID.json" << EOF
{
  "id": "$APPROVAL_ID",
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "tool": "$TOOL_NAME",
  "command": "$COMMAND",
  "file": "$FILE_PATH",
  "status": "pending",
  "timeout_seconds": 300
}
EOF

echo "APPROVAL REQUIRED"
echo "Operation: $TOOL_NAME"
echo "Detail: ${COMMAND:-$FILE_PATH}"
echo "ID: $APPROVAL_ID"
echo "Approve via remote control or: touch $APPROVAL_DIR/$APPROVAL_ID.approved"

TIMEOUT=300
ELAPSED=0

while [ $ELAPSED -lt $TIMEOUT ]; do
    if [ -f "$APPROVAL_DIR/$APPROVAL_ID.approved" ]; then
        echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),APPROVED,$TOOL_NAME,${COMMAND:-$FILE_PATH}" >> "$AUDIT_LOG"
        rm -f "$APPROVAL_DIR/$APPROVAL_ID.json" "$APPROVAL_DIR/$APPROVAL_ID.approved"
        echo "APPROVED"
        exit 0
    fi

    if [ -f "$APPROVAL_DIR/$APPROVAL_ID.rejected" ]; then
        echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),REJECTED,$TOOL_NAME,${COMMAND:-$FILE_PATH}" >> "$AUDIT_LOG"
        rm -f "$APPROVAL_DIR/$APPROVAL_ID.json" "$APPROVAL_DIR/$APPROVAL_ID.rejected"
        echo "REJECTED"
        exit 2
    fi

    sleep 5
    ELAPSED=$((ELAPSED + 5))
done

echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),TIMEOUT,$TOOL_NAME,${COMMAND:-$FILE_PATH}" >> "$AUDIT_LOG"
rm -f "$APPROVAL_DIR/$APPROVAL_ID.json"
echo "TIMEOUT: No approval in ${TIMEOUT}s — operation rejected"
exit 2
chmod +x scripts/hooks/approval-gate.sh

Step 11: Team Subagents

Backend Specialist

Create .claude/agents/backend-specialist.md:

---
name: backend-specialist
description: ShopFlow backend specialist. Implements services, repositories, and models.
tools: Read, Write, Edit, Grep, Glob, Bash
hooks:
  PreToolUse:
    - matcher: "Write|Edit"
      hooks:
        - type: command
          command: |
            #!/bin/bash
            INPUT=$(cat -)
            FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
            if [ -n "$FILE" ] && ! echo "$FILE" | grep -qE "^(app/|tests/|scripts/)"; then
              echo "BLOCKED: backend-specialist can only edit app/, tests/, scripts/"
              exit 2
            fi
            exit 0
---

You are the backend specialist for ShopFlow API. You implement business logic in services, data access in repositories, and database models.

Follow the project CLAUDE.md strictly. Key rules:
- Type hints on all functions
- Repository Pattern: services call repositories, never access DB directly
- Async/await for all I/O
- Use structlog, never print()
- Factory pattern for test data

Review Agent

Create .claude/agents/review-agent.md:

---
name: review-agent
description: Code review specialist. Reads code and reports issues. Does NOT modify files.
tools: Read, Grep, Glob
---

You are the code review agent for ShopFlow API. You review code for compliance with the project CLAUDE.md.

Your process:
1. Read the files specified in the task
2. Check against CLAUDE.md rules: type hints, naming, architecture layers, forbidden patterns
3. Report violations with file, line, and specific rule violated
4. Suggest fixes but do NOT edit files

Output format:
- PASS: No violations found
- VIOLATIONS: List each with file:line — rule — description

Step 12: Remote Monitoring Script

Create scripts/monitor-remote.py:

#!/usr/bin/env python3
"""
ShopFlow API — Remote Monitor
Monitors Claude Code sessions and pending approvals.

Usage:
    python scripts/monitor-remote.py <session_id> <token>
    python scripts/monitor-remote.py --local  (local mode, without remote control)
"""

import json
import sys
import time
from pathlib import Path
from datetime import datetime

APPROVALS_DIR = Path(".claude/approvals")
LOGS_DIR = Path(".claude/logs")


def check_local_approvals():
    pending = []
    if not APPROVALS_DIR.exists():
        return pending

    for f in APPROVALS_DIR.glob("*.json"):
        try:
            data = json.loads(f.read_text())
            if data.get("status") == "pending":
                pending.append(data)
        except (json.JSONDecodeError, KeyError):
            continue
    return pending


def approve_local(approval_id):
    approval_file = APPROVALS_DIR / f"{approval_id}.approved"
    approval_file.touch()
    print(f"Approved: {approval_id}")


def reject_local(approval_id):
    approval_file = APPROVALS_DIR / f"{approval_id}.rejected"
    approval_file.touch()
    print(f"Rejected: {approval_id}")


def display_local_dashboard():
    print(f"\033[2J\033[H")
    print(f"{'='*55}")
    print(f"  SHOPFLOW API — LOCAL MONITOR")
    print(f"  {datetime.now().strftime('%H:%M:%S')}")
    print(f"{'='*55}")

    pending = check_local_approvals()
    if pending:
        print(f"\n  ⚠️  PENDING APPROVALS ({len(pending)}):\n")
        for p in pending:
            print(f"  ID:   {p['id']}")
            print(f"  Tool: {p.get('tool', '?')}")
            detail = p.get('command') or p.get('file') or 'N/A'
            print(f"  Info: {detail}")
            print(f"  Time: {p.get('timestamp', '?')}")
            print()
    else:
        print(f"\n  ✅ No pending approvals\n")

    log_file = LOGS_DIR / "enforcement.log"
    if log_file.exists():
        lines = log_file.read_text().strip().split("\n")
        recent = lines[-5:] if len(lines) > 5 else lines
        print(f"  Recent enforcement ({len(lines)} total):")
        for line in recent:
            print(f"    {line}")

    audit_file = LOGS_DIR / "approval-audit.csv"
    if audit_file.exists():
        lines = audit_file.read_text().strip().split("\n")
        if len(lines) > 1:
            print(f"\n  Approval history ({len(lines)-1} decisions):")
            for line in lines[-3:]:
                print(f"    {line}")

    print(f"\n{'='*55}")


def interactive_mode():
    print("ShopFlow API — Local Approval Monitor")
    print("Commands: [a]pprove <id>, [r]eject <id>, [q]uit\n")

    while True:
        display_local_dashboard()

        pending = check_local_approvals()
        if pending:
            try:
                cmd = input("\nCommand (a/r/q): ").strip().lower()
            except (EOFError, KeyboardInterrupt):
                break

            parts = cmd.split(maxsplit=1)
            if not parts:
                continue

            if parts[0] == "q":
                break
            elif parts[0] == "a" and len(parts) == 2:
                approve_local(parts[1])
            elif parts[0] == "r" and len(parts) == 2:
                reject_local(parts[1])
            else:
                print("Unknown command. Use: a <id>, r <id>, q")
        else:
            time.sleep(5)


def main():
    if len(sys.argv) < 2:
        print("Usage:")
        print("  python scripts/monitor-remote.py --local")
        print("  python scripts/monitor-remote.py <session_id> <token>")
        sys.exit(1)

    if sys.argv[1] == "--local":
        interactive_mode()
    else:
        print("Remote monitoring requires Claude Code remote control.")
        print("Falling back to local mode...")
        interactive_mode()


if __name__ == "__main__":
    main()
chmod +x scripts/monitor-remote.py

Step 13: Onboarding Verification Script

Create scripts/verify-setup.sh:

#!/bin/bash

echo "============================================"
echo "  ShopFlow API — Setup Verification"
echo "============================================"
echo ""

ERRORS=0
WARNINGS=0

check() {
    local description=$1
    local condition=$2

    if eval "$condition"; then
        echo "  ✅ $description"
    else
        echo "  ❌ $description"
        ERRORS=$((ERRORS + 1))
    fi
}

warn() {
    local description=$1
    local condition=$2

    if eval "$condition"; then
        echo "  ✅ $description"
    else
        echo "  ⚠️  $description"
        WARNINGS=$((WARNINGS + 1))
    fi
}

echo "Files:"
check "CLAUDE.md exists" "[ -f CLAUDE.md ]"
check ".claude/settings.json exists" "[ -f .claude/settings.json ]"
check "settings.json has hooks" "jq -e '.hooks' .claude/settings.json > /dev/null 2>&1"
check "app/CLAUDE.md exists" "[ -f app/CLAUDE.md ]"
check "app/api/CLAUDE.md exists" "[ -f app/api/CLAUDE.md ]"
check "tests/CLAUDE.md exists" "[ -f tests/CLAUDE.md ]"

echo ""
echo "Hook scripts:"
HOOK_DIR="scripts/hooks"
for script in session-setup.sh block-destructive.sh enforce-conventions.sh enforce-architecture.sh enforce-security.sh approval-gate.sh; do
    check "$script exists and is executable" "[ -x $HOOK_DIR/$script ]"
done

echo ""
echo "Agents:"
check "backend-specialist.md exists" "[ -f .claude/agents/backend-specialist.md ]"
check "review-agent.md exists" "[ -f .claude/agents/review-agent.md ]"

echo ""
echo "Directories:"
check ".claude/approvals/ exists" "[ -d .claude/approvals ]"
check ".claude/logs/ exists" "[ -d .claude/logs ]"

echo ""
echo "Tools:"
warn "ruff installed" "command -v ruff &> /dev/null"
warn "mypy installed" "command -v mypy &> /dev/null"
warn "jq installed" "command -v jq &> /dev/null"
warn "git initialized" "git rev-parse --git-dir > /dev/null 2>&1"

echo ""
echo "============================================"
if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
    echo "  ✅ ALL CHECKS PASSED"
elif [ $ERRORS -eq 0 ]; then
    echo "  ⚠️  $WARNINGS warnings (non-critical)"
else
    echo "  ❌ $ERRORS errors, $WARNINGS warnings"
fi
echo "============================================"

exit $ERRORS
chmod +x scripts/verify-setup.sh

Step 14: Onboarding Documentation

Create docs/ONBOARDING.md:

# ShopFlow API — Developer Onboarding

## Quick Start (5 minutes)

### 1. Clone the repo
```bash
git clone <repo-url>
cd shopflow-api

2. Verify setup

./scripts/verify-setup.sh

3. Install missing tools (if any)

# macOS
brew install jq
pip install ruff mypy

# Ubuntu/Debian
sudo apt-get install jq
pip install ruff mypy

4. Start working

Open Claude Code in the project. The CLAUDE.md and hooks are loaded automatically.

What Happens Automatically

When you open Claude Code in this project:

  • SessionStart hook verifies your environment (git, tools, clean workspace)
  • CLAUDE.md loads the team standard (conventions, architecture, prohibitions)
  • PostToolUse hooks validate every file edit against the standard
  • PreToolUse hooks block destructive operations (force push, DROP TABLE)
  • Approval flows pause sensitive operations until you approve them

You don't need to configure anything. It just works.

Key Files

FilePurpose
CLAUDE.mdTeam standard — READ THIS FIRST
.claude/settings.jsonHooks and remote control configuration
app/CLAUDE.mdBackend-specific rules
app/api/CLAUDE.mdAPI endpoint-specific rules
tests/CLAUDE.mdTesting-specific rules
scripts/hooks/Enforcement scripts
.claude/agents/Team subagents

Remote Control

To monitor a session from another device:

claude --remote
# Note the session ID and token
# From another device:
claude remote status --session <ID> --token <TOKEN>

To approve operations from another terminal:

python scripts/monitor-remote.py --local

Questions?

Read the CLAUDE.md first. If your question isn't answered there, ask in the #shopflow-dev channel.


Create `.github/CONTRIBUTING.md`:

```markdown
# Contributing to ShopFlow API

## Before You Start

1. Read `CLAUDE.md` in the project root — it's the team standard
2. Run `./scripts/verify-setup.sh` to verify your environment
3. Read `docs/ONBOARDING.md` for the full onboarding guide

## Development Workflow

1. Create a feature branch: `git checkout -b feature/my-feature`
2. Make changes using Claude Code (hooks enforce the standard automatically)
3. Run tests: `python -m pytest tests/ -v`
4. Commit with conventional format: `feat(auth): add JWT refresh endpoint`
5. Create a PR with description of what, why, and how to test

## Claude Code Agents

- `backend-specialist`: For implementing services, models, repositories
- `review-agent`: For code review against CLAUDE.md standards

## Approval Flows

Some operations require manual approval:
- `git push --force` → Always blocked
- `git push` to non-feature branches → Requires approval
- Database migrations → Requires approval
- File modifications outside `app/`, `tests/`, `scripts/` → Requires approval

Use `python scripts/monitor-remote.py --local` to manage approvals.

Step 15: Verify the Entire Setup

Test 1: Structure verification

./scripts/verify-setup.sh

Expected result: all checks pass (green). Warnings for uninstalled tools are acceptable.

Test 2: Enforcement hooks test

echo '{"tool_name":"Edit","tool_input":{"file_path":"app/services/user_service.py"}}' | \
    ./scripts/hooks/enforce-conventions.sh
echo "Exit code: $?"

echo '{"tool_name":"Edit","tool_input":{"file_path":"app/api/routes.py"}}' | \
    ./scripts/hooks/enforce-architecture.sh
echo "Exit code: $?"

Test 3: Destructive blocking test

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | \
    ./scripts/hooks/block-destructive.sh
echo "Exit code: $?"

echo '{"tool_name":"Bash","tool_input":{"command":"ls -la"}}' | \
    ./scripts/hooks/block-destructive.sh
echo "Exit code: $?"

Expected result:

  • rm -rf / → exit 2 (blocked)
  • ls -la → exit 0 (allowed)

Test 4: Security enforcement test

echo '{"tool_name":"Write","tool_input":{"file_path":".env.production"}}' | \
    ./scripts/hooks/enforce-security.sh
echo "Exit code: $?"

echo '{"tool_name":"Write","tool_input":{"file_path":"app/services/auth.py"}}' | \
    ./scripts/hooks/enforce-security.sh
echo "Exit code: $?"

Expected result:

  • .env.production → exit 2 (blocked)
  • app/services/auth.py → exit 0 (if it has no hardcoded secrets)

Test 5: Approval flow test

# Terminal 1: Simulate an approval request
echo '{"tool_name":"Bash","tool_input":{"command":"git push origin main"}}' | \
    ./scripts/hooks/approval-gate.sh &

# Terminal 2: Approve the request
sleep 2
APPROVAL_ID=$(ls .claude/approvals/ | grep "approval-" | sed 's/.json//' | head -1)
touch ".claude/approvals/$APPROVAL_ID.approved"

Success checklist

✅ CLAUDE.md in the root with all team sections
✅ CLAUDE.md in app/, app/api/, tests/ with specific rules
✅ settings.json with SessionStart, PreToolUse, PostToolUse, PermissionRequest hooks
✅ 6 hook scripts, all executable
✅ 2 subagents configured with restrictions
✅ Functional remote monitoring script
✅ Setup verification script passes without errors
✅ ONBOARDING.md with a 5-minute checklist
✅ CONTRIBUTING.md with a link to CLAUDE.md
✅ Blocking of destructive operations works (exit 2)
✅ Approval flow creates requests and waits for a response
✅ Enforcement hooks detect convention violations

Common Errors and Solutions

Error 1: "Permission denied" on hook scripts

Symptom: The hooks don't run and Claude Code reports a permissions error.

Cause: The scripts don't have execute permissions.

Solution:

chmod +x scripts/hooks/*.sh
ls -la scripts/hooks/

Error 2: "jq: command not found" in hooks

Symptom: Hooks that parse JSON fail silently. Validations don't work.

Cause: jq is not installed.

Solution:

# macOS
brew install jq

# Ubuntu/Debian
sudo apt-get install jq

# Verify
jq --version

Error 3: "Enforcement hook is too strict"

Symptom: Claude Code can't complete any task because the hooks reject everything.

Cause: The detection patterns are too broad (false positives).

Solution: Add exceptions for legitimate cases:

# In enforce-conventions.sh, allow print() in CLI scripts:
if echo "$FILE" | grep -q "^scripts/"; then
    exit 0  # No enforcement in utility scripts
fi

Error 4: "Approval timeout too short"

Symptom: Operations get rejected by timeout before you can review them.

Cause: The default timeout of 300 seconds (5 minutes) isn't enough.

Solution: Increase the timeout in approval-gate.sh:

TIMEOUT=600  # 10 minutes instead of 5

Error 5: "The subdirectory CLAUDE.md doesn't apply"

Symptom: Claude Code seems to ignore the subdirectory rules.

Cause: The CLAUDE.md file is in the wrong location or has a different name.

Solution: Verify that:

  1. The file is named exactly CLAUDE.md (uppercase)
  2. It's in the right directory: app/api/CLAUDE.md, not app/api/claude.md
  3. Claude Code has read access to the file
find . -name "CLAUDE.md" -type f

Error 6: "The hooks run on every tool and make everything slow"

Symptom: Claude Code takes 3-5 seconds on every operation because of the enforcement hooks.

Cause: The hooks run ruff, mypy, and grep on every edit.

Solution: Optimize the hooks to be more selective:

# Only run ruff if the file actually changed
FILE_HASH=$(md5 -q "$FILE" 2>/dev/null || md5sum "$FILE" | cut -d' ' -f1)
HASH_FILE="/tmp/hook-hash-$(echo "$FILE" | md5 -q 2>/dev/null || echo "$FILE" | md5sum | cut -c1-8)"
if [ -f "$HASH_FILE" ] && [ "$(cat "$HASH_FILE")" = "$FILE_HASH" ]; then
    exit 0  # This content was already validated
fi
echo "$FILE_HASH" > "$HASH_FILE"

Error 7: "The review-agent tries to edit files"

Symptom: The review-agent reports errors because it doesn't have edit permissions.

Cause: The agent's prompt or the task asks it to make changes, but its tools only include Read, Grep, Glob.

Solution: This is intentional. The review-agent is read-only. If you need it to apply fixes, use the backend-specialist with the review-agent's findings as input.

Error 8: "Approval files pile up in .claude/approvals/"

Symptom: The directory fills up with .json files from old requests.

Cause: Files from expired approvals aren't cleaned up.

Solution: The SessionStart hook already includes cleanup of files older than 60 minutes. If you need manual cleanup:

find .claude/approvals/ -name "*.json" -mmin +60 -delete

Connection to the Next Module

You've built a complete team setup. The CLAUDE.md is the constitution, the hooks are the guardians, remote control is the monitoring center, and the onboarding process makes any new member adopt the standard automatically.

Module 8: Capstone Project uses this setup as its operational base. The CLAUDE.md defines the rules that all agents of the multi-agent system respect. The hooks validate every action of every agent. Remote control lets you approve critical operations during execution. The subagents you defined work within the restrictions you configured.

Everything you built in Modules 1-7 comes together in the final project: specialized subagents (M1) with shared memory (M2) working in parallel (M3) as an Agent Team (M4), packaged in plugins (M5), automated with hooks and SDK (M6), operated remotely with team standards (M7). Module 8 is the proof that everything works together.


Summary

  • You built a complete team setup with CLAUDE.md, enforcement hooks, approval flows, remote control, and onboarding
  • The main CLAUDE.md defines conventions, architecture, prohibitions, testing, git, and security for the whole team
  • Subdirectory CLAUDE.md files (app/, app/api/, tests/) add module-specific rules
  • 6 hooks cover the whole cycle: SessionStart (setup), PreToolUse (blocking), PostToolUse (enforcement ×3), PermissionRequest (approval)
  • The enforcement hooks validate conventions (naming, imports, print), architecture (layers, no circular), and security (no secrets)
  • The approval flow pauses sensitive operations, writes requests to files, and waits for approval with a 5-minute timeout
  • 2 subagents (backend-specialist, review-agent) work within the team's restrictions
  • The monitoring script lets you manage approvals locally or via remote control
  • The verification script validates that the whole setup is complete in a single run
  • ONBOARDING.md and CONTRIBUTING.md document the process for new members
  • This setup is the operational base of Module 8 (Capstone Project)

Project Resources

  1. Claude Code CLAUDE.md Memory — Official documentation of CLAUDE.md and its levels
  2. Claude Code Settings — settings.json and hooks configuration
  3. Claude Code Hooks — Complete hooks documentation
  4. Claude Code Subagents — Configuring subagents with restrictions
  5. Claude Code Best Practices — Best practices for teams
  6. Conventional Commits — Conventional commits specification

Next module: Module 8 (Capstone Project: Complete Multi-Agent System) integrates everything built in Modules 1-7. The specialized subagents with shared memory work in parallel as an Agent Team, packaged in plugins, automated with hooks and SDK, operated remotely with the CLAUDE.md as constitution. It's the closing of the guide — a complete multi-agent system that works from start to finish.