Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions .conductor/conductor_setup/cache_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""High-performance cache for all setup operations."""

import json
import hashlib
import time
from pathlib import Path
from typing import Any, Callable, Optional, Dict


class SetupCache:
"""Cache detection results and API calls for speed."""

def __init__(self):
self.cache_dir = Path.home() / ".conductor" / ".cache"
self.cache_dir.mkdir(parents=True, exist_ok=True)
self.memory_cache: Dict[str, Dict[str, Any]] = {}

def get(self, key: str) -> Optional[Any]:
"""Get from memory cache first, then disk."""
# Memory cache (fastest)
if key in self.memory_cache:
entry = self.memory_cache[key]
if time.time() - entry["timestamp"] < entry["ttl"]:
return entry["value"]

# Disk cache (fast)
cache_file = self.cache_dir / f"{key}.json"
if cache_file.exists():
try:
data = json.loads(cache_file.read_text())
if time.time() - data["timestamp"] < data["ttl"]:
# Populate memory cache
self.memory_cache[key] = data
return data["value"]
except Exception:
# Invalid cache file, ignore
pass

return None

def set(self, key: str, value: Any, ttl: int = 3600) -> None:
"""Set in both memory and disk cache."""
entry = {"value": value, "timestamp": time.time(), "ttl": ttl}

# Memory cache
self.memory_cache[key] = entry

# Disk cache
cache_file = self.cache_dir / f"{key}.json"
try:
cache_file.write_text(json.dumps(entry, indent=2))
except Exception:
# Ignore cache write failures
pass

def get_or_compute(self, key: str, compute_fn: Callable, ttl: int = 3600) -> Any:
"""Get from cache or compute and cache result."""
cached = self.get(key)
if cached is not None:
return cached

value = compute_fn()
self.set(key, value, ttl)
return value

def clear(self) -> None:
"""Clear all caches."""
self.memory_cache.clear()
try:
for cache_file in self.cache_dir.glob("*.json"):
cache_file.unlink()
except Exception:
# Ignore cache clear failures
pass

def get_project_hash(self, project_root: Path) -> str:
"""Generate unique hash for project state."""
key_files = [
"package.json",
"pyproject.toml",
"Cargo.toml",
"go.mod",
"requirements.txt",
"Gemfile",
"pom.xml",
"build.gradle",
]
hasher = hashlib.md5()

# Hash key files that define dependencies
for file_name in key_files:
file_path = project_root / file_name
if file_path.exists():
try:
hasher.update(file_path.read_bytes())
except Exception:
# Ignore read errors
pass

# Hash directory structure for better cache invalidation
try:
for p in sorted(project_root.rglob("*")):
if p.is_file() and not any(
skip in str(p) for skip in [".git", "__pycache__", "node_modules"]
):
hasher.update(str(p.relative_to(project_root)).encode())
except Exception:
# Ignore traversal errors
pass

return hasher.hexdigest()[:12]


# Global cache instance
_cache = None


def get_cache() -> SetupCache:
"""Get global cache instance (singleton)."""
global _cache
if _cache is None:
_cache = SetupCache()
return _cache
8 changes: 8 additions & 0 deletions .conductor/conductor_setup/config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
Configuration management sub-modules
"""

from .express_configs import EXPRESS_CONFIGS, get_express_config
from .interactive import InteractiveConfigurator

__all__ = ["EXPRESS_CONFIGS", "get_express_config", "InteractiveConfigurator"]
206 changes: 206 additions & 0 deletions .conductor/conductor_setup/config/express_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
"""
Express configuration presets for common project types
"""

from typing import Dict, Any, Optional

# Express configurations for common project types
EXPRESS_CONFIGS = {
"react-typescript": {
"patterns": ["react", "typescript", "tsx", "jsx"],
"roles": {"default": "dev", "specialized": ["frontend", "code-reviewer"]},
"github_integration": {"issue_to_task": True, "pr_reviews": True},
"build_validation": ["npm test", "npm run build"],
"suggested_tasks": [
{
"title": "Set up component testing with React Testing Library",
"labels": ["conductor:task", "testing", "frontend"],
},
{
"title": "Add Storybook for component development",
"labels": ["conductor:task", "enhancement", "frontend"],
},
{
"title": "Configure ESLint and Prettier",
"labels": ["conductor:task", "code-quality", "dev-experience"],
},
],
},
"python-fastapi": {
"patterns": ["fastapi", "python", "uvicorn", "pydantic"],
"roles": {"default": "dev", "specialized": ["backend", "code-reviewer"]},
"github_integration": {"issue_to_task": True, "pr_reviews": True},
"build_validation": ["pytest", "black --check ."],
"suggested_tasks": [
{
"title": "Add API documentation with OpenAPI",
"labels": ["conductor:task", "documentation", "backend"],
},
{
"title": "Set up database migrations with Alembic",
"labels": ["conductor:task", "database", "backend"],
},
{
"title": "Add integration tests for endpoints",
"labels": ["conductor:task", "testing", "backend"],
},
],
},
"nextjs-fullstack": {
"patterns": ["next", "react", "vercel"],
"roles": {
"default": "dev",
"specialized": ["frontend", "backend", "code-reviewer"],
},
"github_integration": {"issue_to_task": True, "pr_reviews": True},
"build_validation": ["npm test", "npm run build", "npm run lint"],
"suggested_tasks": [
{
"title": "Set up authentication with NextAuth.js",
"labels": ["conductor:task", "auth", "fullstack"],
},
{
"title": "Configure Prisma for database access",
"labels": ["conductor:task", "database", "backend"],
},
{
"title": "Add E2E tests with Playwright",
"labels": ["conductor:task", "testing", "e2e"],
},
],
},
"vue-javascript": {
"patterns": ["vue", "nuxt", "vite"],
"roles": {"default": "dev", "specialized": ["frontend", "code-reviewer"]},
"github_integration": {"issue_to_task": True, "pr_reviews": True},
"build_validation": ["npm test", "npm run build"],
"suggested_tasks": [
{
"title": "Set up Pinia for state management",
"labels": ["conductor:task", "state-management", "frontend"],
},
{
"title": "Add component testing with Vitest",
"labels": ["conductor:task", "testing", "frontend"],
},
{
"title": "Configure Vue Router for navigation",
"labels": ["conductor:task", "routing", "frontend"],
},
],
},
"python-django": {
"patterns": ["django", "python", "wsgi"],
"roles": {"default": "dev", "specialized": ["backend", "code-reviewer"]},
"github_integration": {"issue_to_task": True, "pr_reviews": True},
"build_validation": ["python manage.py test", "black --check ."],
"suggested_tasks": [
{
"title": "Set up Django REST framework",
"labels": ["conductor:task", "api", "backend"],
},
{
"title": "Configure Celery for async tasks",
"labels": ["conductor:task", "async", "backend"],
},
{
"title": "Add Django Debug Toolbar",
"labels": ["conductor:task", "dev-experience", "backend"],
},
],
},
"go-microservices": {
"patterns": ["go", "gin", "fiber", "echo"],
"roles": {"default": "dev", "specialized": ["backend", "code-reviewer"]},
"github_integration": {"issue_to_task": True, "pr_reviews": True},
"build_validation": ["go test ./...", "go build"],
"suggested_tasks": [
{
"title": "Set up structured logging with zerolog",
"labels": ["conductor:task", "observability", "backend"],
},
{
"title": "Add OpenTelemetry instrumentation",
"labels": ["conductor:task", "observability", "backend"],
},
{
"title": "Create Dockerfile and docker-compose",
"labels": ["conductor:task", "devops", "containers"],
},
],
},
"rust-cli": {
"patterns": ["rust", "cargo", "clap"],
"roles": {"default": "dev", "specialized": ["backend", "code-reviewer"]},
"github_integration": {"issue_to_task": True, "pr_reviews": True},
"build_validation": ["cargo test", "cargo build --release"],
"suggested_tasks": [
{
"title": "Add comprehensive CLI tests",
"labels": ["conductor:task", "testing", "cli"],
},
{
"title": "Set up GitHub release workflow",
"labels": ["conductor:task", "ci-cd", "devops"],
},
{
"title": "Add shell completion generation",
"labels": ["conductor:task", "enhancement", "cli"],
},
],
},
"mobile-react-native": {
"patterns": ["react-native", "expo", "ios", "android"],
"roles": {"default": "dev", "specialized": ["mobile", "code-reviewer"]},
"github_integration": {"issue_to_task": True, "pr_reviews": True},
"build_validation": ["npm test", "npm run lint"],
"suggested_tasks": [
{
"title": "Set up Detox for E2E testing",
"labels": ["conductor:task", "testing", "mobile"],
},
{
"title": "Configure push notifications",
"labels": ["conductor:task", "feature", "mobile"],
},
{
"title": "Add crash reporting with Sentry",
"labels": ["conductor:task", "monitoring", "mobile"],
},
],
},
}


def get_express_config(stack_info: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Match detected stack to express config"""
# Use the primary stack from summary if available
if stack_info.get("summary", {}).get("primary_stack"):
stack_name = stack_info["summary"]["primary_stack"]
if stack_name in EXPRESS_CONFIGS:
return EXPRESS_CONFIGS[stack_name]

# Otherwise try pattern matching
detected_items = set()
detected_items.update(stack_info.get("frameworks", []))
detected_items.update(stack_info.get("summary", {}).get("languages", []))
detected_items.update(stack_info.get("summary", {}).get("tools", []))

# Add items from modern tools
modern = stack_info.get("modern_tools", {})
if modern.get("framework"):
detected_items.add(modern["framework"])
if modern.get("build_tool"):
detected_items.add(modern["build_tool"])

# Find best match
best_match = None
best_score = 0

for stack_name, config in EXPRESS_CONFIGS.items():
score = len(detected_items.intersection(config["patterns"]))
if score > best_score:
best_match = stack_name
best_score = score

return EXPRESS_CONFIGS.get(best_match) if best_match and best_score > 0 else None
Loading