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.jsonwith 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
| File | Purpose |
|---|---|
CLAUDE.md | Team standard — READ THIS FIRST |
.claude/settings.json | Hooks and remote control configuration |
app/CLAUDE.md | Backend-specific rules |
app/api/CLAUDE.md | API endpoint-specific rules |
tests/CLAUDE.md | Testing-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:
- The file is named exactly
CLAUDE.md(uppercase) - It's in the right directory:
app/api/CLAUDE.md, notapp/api/claude.md - 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
- Claude Code CLAUDE.md Memory — Official documentation of CLAUDE.md and its levels
- Claude Code Settings — settings.json and hooks configuration
- Claude Code Hooks — Complete hooks documentation
- Claude Code Subagents — Configuring subagents with restrictions
- Claude Code Best Practices — Best practices for teams
- 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.