TradingAgents/.claude/hooks/setup.py

545 lines
19 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Automated setup script for autonomous-dev plugin.
Copies hooks and templates from plugin directory to project,
then configures based on user preferences.
Supports both interactive and non-interactive modes for:
- Plugin file copying (hooks, templates)
- Hook configuration (slash commands vs automatic)
- PROJECT.md template installation
- GitHub authentication setup
- Settings validation
Usage:
Interactive: python .claude/scripts/setup.py
Automated: python .claude/scripts/setup.py --auto --hooks=slash-commands --github
Team install: python .claude/scripts/setup.py --preset=team
"""
import argparse
import json
import shutil
import sys
from pathlib import Path
from typing import Optional
class SetupWizard:
"""Interactive and automated setup for autonomous-dev plugin."""
def __init__(self, auto: bool = False, preset: Optional[str] = None):
self.auto = auto
self.preset = preset
self.project_root = Path.cwd()
self.claude_dir = self.project_root / ".claude"
self.plugin_dir = self.claude_dir / "plugins" / "autonomous-dev"
# Configuration choices
self.config = {
"hooks_mode": None, # "slash-commands", "automatic", "custom"
"setup_project_md": None, # True/False
"setup_github": None, # True/False
}
def run(self):
"""Run the setup wizard."""
if not self.auto:
self.print_welcome()
# Verify plugin installation
if not self.verify_plugin_installation():
return
# Load preset if specified
if self.preset:
self.load_preset(self.preset)
else:
# Interactive or manual choices
self.choose_hooks_mode()
self.choose_project_md()
self.choose_github()
# Execute setup based on choices
self.copy_plugin_files()
self.setup_hooks()
self.setup_project_md()
self.setup_github()
self.create_gitignore_entries()
if not self.auto:
self.print_completion()
def verify_plugin_installation(self):
"""Verify the plugin is installed."""
# After /plugin install, files are in .claude/ not .claude/plugins/
# Check if essential files exist
hooks_dir = self.claude_dir / "hooks"
commands_dir = self.claude_dir / "commands"
templates_dir = self.claude_dir / "templates"
# All three directories must exist (consistent with copy_plugin_files logic)
missing = []
if not hooks_dir.exists():
missing.append("hooks")
if not commands_dir.exists():
missing.append("commands")
if not templates_dir.exists():
missing.append("templates")
if missing:
print("\n❌ Plugin not installed or corrupted!")
print(f"\nMissing directories: {', '.join(missing)}")
print("\nTo fix:")
print(" 1. Reinstall plugin (recommended):")
print(" /plugin uninstall autonomous-dev")
print(" (exit and restart Claude Code)")
print(" /plugin install autonomous-dev")
print(" (exit and restart Claude Code)")
print("\n 2. Or verify you've restarted Claude Code after install")
return False
if not self.auto:
print(f"\n✅ Plugin installed in .claude/")
return True
def copy_plugin_files(self):
"""Verify or copy hooks, templates, and commands from plugin to project.
Note: After /plugin install, files are usually already in .claude/
This method verifies they exist and only copies if missing.
"""
# Check if files already installed by /plugin install
dest_hooks = self.claude_dir / "hooks"
dest_templates = self.claude_dir / "templates"
dest_commands = self.claude_dir / "commands"
all_exist = (
dest_hooks.exists() and
dest_templates.exists() and
dest_commands.exists()
)
if all_exist:
if not self.auto:
print(f"\n✅ Plugin files already installed in .claude/")
print(f" Hooks: {len(list(dest_hooks.glob('*.py')))} files")
print(f" Commands: {len(list(dest_commands.glob('*.md')))} files")
return
# If not all exist, try to copy from plugin source (if available)
if not self.auto:
print(f"\n📦 Setting up plugin files...")
# Copy hooks if missing
if not dest_hooks.exists():
src_hooks = self.plugin_dir / "hooks"
if src_hooks.exists():
shutil.copytree(src_hooks, dest_hooks)
if not self.auto:
print(f"\n✅ Copied hooks to: {dest_hooks}")
else:
print(f"\n⚠️ Warning: Hooks directory not found", file=sys.stderr)
# Copy templates if missing
if not dest_templates.exists():
src_templates = self.plugin_dir / "templates"
if src_templates.exists():
shutil.copytree(src_templates, dest_templates)
if not self.auto:
print(f"\n✅ Copied templates to: {dest_templates}")
else:
print(f"\n⚠️ Warning: Templates directory not found", file=sys.stderr)
# Copy commands if missing
if not dest_commands.exists():
src_commands = self.plugin_dir / "commands"
if src_commands.exists():
shutil.copytree(src_commands, dest_commands)
if not self.auto:
print(f"\n✅ Copied commands to: {dest_commands}")
else:
print(f"\n⚠️ Warning: Commands directory not found", file=sys.stderr)
def print_welcome(self):
"""Print welcome message."""
print("\n" + "" * 60)
print("🚀 Autonomous Development Plugin Setup")
print("" * 60)
print("\nThis wizard will configure:")
print(" ✓ Hooks (automatic quality checks)")
print(" ✓ Templates (PROJECT.md)")
print(" ✓ GitHub integration (optional)")
print("\nThis takes about 2-3 minutes.\n")
def load_preset(self, preset: str):
"""Load preset configuration."""
presets = {
"minimal": {
"hooks_mode": "slash-commands",
"setup_project_md": True,
"setup_github": False,
},
"team": {
"hooks_mode": "automatic",
"setup_project_md": True,
"setup_github": True,
},
"solo": {
"hooks_mode": "slash-commands",
"setup_project_md": True,
"setup_github": False,
},
"power-user": {
"hooks_mode": "automatic",
"setup_project_md": True,
"setup_github": True,
},
}
if preset not in presets:
print(f"❌ Unknown preset: {preset}")
print(f"Available presets: {', '.join(presets.keys())}")
sys.exit(1)
self.config.update(presets[preset])
if not self.auto:
print(f"\n✅ Loaded preset: {preset}")
def choose_hooks_mode(self):
"""Choose hooks mode (interactive or from args)."""
if self.auto:
return # Already set via args
print("\n" + "" * 60)
print("📋 Choose Your Workflow")
print("" * 60)
print("\nHow would you like to run quality checks?\n")
print("[1] Slash Commands (Recommended for beginners)")
print(" - Explicit control: run /format, /test when you want")
print(" - Great for learning the workflow")
print(" - No surprises or automatic changes\n")
print("[2] Automatic Hooks (Power users)")
print(" - Auto-format on save")
print(" - Auto-test on commit")
print(" - Fully automated quality enforcement\n")
print("[3] Custom (I'll configure manually later)\n")
while True:
choice = input("Your choice [1/2/3]: ").strip()
if choice == "1":
self.config["hooks_mode"] = "slash-commands"
break
elif choice == "2":
self.config["hooks_mode"] = "automatic"
break
elif choice == "3":
self.config["hooks_mode"] = "custom"
break
else:
print("Invalid choice. Please enter 1, 2, or 3.")
def choose_project_md(self):
"""Choose whether to setup PROJECT.md."""
if self.auto:
return
print("\n" + "" * 60)
print("📄 PROJECT.md Template Setup")
print("" * 60)
print("\nPROJECT.md defines your project's strategic direction.")
print("All agents validate against it before working.\n")
# Check if PROJECT.md already exists
project_md = self.claude_dir / "PROJECT.md"
if project_md.exists():
print(f"⚠️ PROJECT.md already exists at: {project_md}")
choice = input("Overwrite with template? [y/N]: ").strip().lower()
self.config["setup_project_md"] = choice == "y"
else:
choice = input("Create PROJECT.md from template? [Y/n]: ").strip().lower()
self.config["setup_project_md"] = choice != "n"
def choose_github(self):
"""Choose whether to setup GitHub integration."""
if self.auto:
return
print("\n" + "" * 60)
print("🔗 GitHub Integration (Optional)")
print("" * 60)
print("\nGitHub integration enables:")
print(" ✓ Sprint tracking via Milestones")
print(" ✓ Issue management")
print(" ✓ PR automation\n")
choice = input("Setup GitHub integration? [y/N]: ").strip().lower()
self.config["setup_github"] = choice == "y"
def setup_hooks(self):
"""Configure hooks based on chosen mode."""
if self.config["hooks_mode"] == "custom":
if not self.auto:
print("\n✅ Custom mode - No automatic hook configuration")
return
if self.config["hooks_mode"] == "slash-commands":
if not self.auto:
print("\n✅ Slash Commands Mode Selected")
print("\nYou can run these commands anytime:")
print(" /format Format code")
print(" /test Run tests")
print(" /security-scan Security check")
print(" /full-check All checks")
print("\n✅ No additional configuration needed.")
return
# Automatic hooks mode
settings_file = self.claude_dir / "settings.local.json"
hooks_config = {
"hooks": {
"PostToolUse": {
"Write": ["python .claude/hooks/auto_format.py"],
"Edit": ["python .claude/hooks/auto_format.py"],
},
"PreCommit": {
"*": [
"python .claude/hooks/auto_test.py",
"python .claude/hooks/security_scan.py",
]
},
}
}
# Merge with existing settings if present
if settings_file.exists():
with open(settings_file) as f:
existing = json.load(f)
existing.update(hooks_config)
hooks_config = existing
with open(settings_file, "w") as f:
json.dump(hooks_config, f, indent=2)
if not self.auto:
print("\n⚙️ Configuring Automatic Hooks...")
print(f"\n✅ Created: {settings_file}")
print("\nWhat will happen automatically:")
print(" ✓ Code formatted after every write/edit")
print(" ✓ Tests run before every commit")
print(" ✓ Security scan before every commit")
def setup_project_md(self):
"""Setup PROJECT.md from template."""
if not self.config["setup_project_md"]:
return
template_path = self.claude_dir / "templates" / "PROJECT.md"
target_path = self.claude_dir / "PROJECT.md"
if not template_path.exists():
print(f"\n⚠️ Template not found: {template_path}")
print(" Run /plugin install autonomous-dev first")
return
shutil.copy(template_path, target_path)
if not self.auto:
print(f"\n✅ Created: {target_path}")
print("\nNext steps:")
print(" 1. Open PROJECT.md in your editor")
print(" 2. Fill in GOALS, SCOPE, CONSTRAINTS")
print(" 3. Save and run: /align-project")
def setup_github(self):
"""Setup GitHub integration."""
if not self.config["setup_github"]:
return
env_file = self.project_root / ".env"
# Create .env if it doesn't exist
if not env_file.exists():
env_content = """# GitHub Personal Access Token
# Get yours at: https://github.com/settings/tokens
# Required scopes: repo, workflow
GITHUB_TOKEN=ghp_your_token_here
"""
env_file.write_text(env_content)
if not self.auto:
print(f"\n✅ Created: {env_file}")
print("\n📝 Next Steps:")
print(" 1. Go to: https://github.com/settings/tokens")
print(" 2. Generate new token (classic)")
print(" 3. Select scopes: repo, workflow")
print(" 4. Copy token and add to .env")
print("\nSee: .claude/docs/GITHUB_AUTH_SETUP.md for details")
else:
if not self.auto:
print(f"\n .env already exists: {env_file}")
print(" Add GITHUB_TOKEN if not already present")
def create_gitignore_entries(self):
"""Ensure .env and other files are gitignored."""
gitignore = self.project_root / ".gitignore"
entries_to_add = [
".env",
".env.local",
".claude/settings.local.json",
]
if gitignore.exists():
existing = gitignore.read_text()
else:
existing = ""
new_entries = []
for entry in entries_to_add:
if entry not in existing:
new_entries.append(entry)
if new_entries:
with open(gitignore, "a") as f:
if not existing.endswith("\n"):
f.write("\n")
f.write("\n# Autonomous-dev plugin (gitignored)\n")
for entry in new_entries:
f.write(f"{entry}\n")
if not self.auto:
print(f"\n✅ Updated: {gitignore}")
print(f" Added: {', '.join(new_entries)}")
def print_completion(self):
"""Print completion message."""
print("\n" + "" * 60)
print("✅ Setup Complete!")
print("" * 60)
print("\nYour autonomous development environment is ready!")
print("\nQuick Start:")
if self.config["hooks_mode"] == "slash-commands":
print(" 1. Describe feature")
print(" 2. Run: /auto-implement")
print(" 3. Before commit: /full-check")
print(" 4. Commit: /commit")
elif self.config["hooks_mode"] == "automatic":
print(" 1. Describe feature")
print(" 2. Run: /auto-implement")
print(" 3. Commit: git commit (hooks run automatically)")
print("\nUseful Commands:")
print(" /align-project Validate alignment")
print(" /auto-implement Autonomous development")
print(" /full-check Run all quality checks")
print(" /help Get help")
print("\nHappy coding! 🚀\n")
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Setup autonomous-dev plugin",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
Interactive mode:
python scripts/setup.py
Automated with slash commands:
python scripts/setup.py --auto --hooks=slash-commands --project-md
Automated with automatic hooks:
python scripts/setup.py --auto --hooks=automatic --project-md --github
Using presets:
python scripts/setup.py --preset=minimal # Slash commands only
python scripts/setup.py --preset=team # Full team setup
python scripts/setup.py --preset=solo # Solo developer
python scripts/setup.py --preset=power-user # Everything enabled
Presets:
minimal: Slash commands + PROJECT.md
solo: Same as minimal
team: Automatic hooks + PROJECT.md + GitHub
power-user: Everything enabled
""",
)
parser.add_argument(
"--auto",
action="store_true",
help="Run in non-interactive mode (requires other flags)",
)
parser.add_argument(
"--preset",
choices=["minimal", "team", "solo", "power-user"],
help="Use preset configuration",
)
parser.add_argument(
"--hooks",
choices=["slash-commands", "automatic", "custom"],
help="Hooks mode (requires --auto)",
)
parser.add_argument(
"--project-md",
action="store_true",
help="Setup PROJECT.md from template (requires --auto)",
)
parser.add_argument(
"--github",
action="store_true",
help="Setup GitHub integration (requires --auto)",
)
parser.add_argument(
"--dev-mode",
action="store_true",
help="Developer mode: skip plugin install verification (for testing from git clone)",
)
args = parser.parse_args()
# Validation
if args.auto and not args.preset:
if not args.hooks:
parser.error("--auto requires --hooks or --preset")
wizard = SetupWizard(auto=args.auto, preset=args.preset)
# Developer mode: skip verification
if args.dev_mode:
print("🔧 Developer mode enabled - skipping plugin verification")
wizard.verify_plugin_installation = lambda: True
# Apply command-line arguments
if args.hooks:
wizard.config["hooks_mode"] = args.hooks
if args.project_md or args.auto:
wizard.config["setup_project_md"] = args.project_md
if args.github or args.auto:
wizard.config["setup_github"] = args.github
try:
wizard.run()
sys.exit(0)
except KeyboardInterrupt:
print("\n\n❌ Setup cancelled by user")
sys.exit(1)
except Exception as e:
print(f"\n❌ Setup failed: {e}")
sys.exit(1)
if __name__ == "__main__":
main()