Skip to content

Commit 4ed1c81

Browse files
authored
Merge pull request #67 from ryanmac/refactor-long-python-files
refactor: Break down long Python files into modular components
2 parents 4ca2f19 + a38342e commit 4ed1c81

30 files changed

+2088
-1574
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""
2+
Configuration management sub-modules
3+
"""
4+
5+
from .express_configs import EXPRESS_CONFIGS, get_express_config
6+
from .interactive import InteractiveConfigurator
7+
8+
__all__ = ["EXPRESS_CONFIGS", "get_express_config", "InteractiveConfigurator"]
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
"""
2+
Express configuration presets for common project types
3+
"""
4+
5+
from typing import Dict, Any, Optional
6+
7+
# Express configurations for common project types
8+
EXPRESS_CONFIGS = {
9+
"react-typescript": {
10+
"patterns": ["react", "typescript", "tsx", "jsx"],
11+
"roles": {"default": "dev", "specialized": ["frontend", "code-reviewer"]},
12+
"github_integration": {"issue_to_task": True, "pr_reviews": True},
13+
"build_validation": ["npm test", "npm run build"],
14+
"suggested_tasks": [
15+
{
16+
"title": "Set up component testing with React Testing Library",
17+
"labels": ["conductor:task", "testing", "frontend"],
18+
},
19+
{
20+
"title": "Add Storybook for component development",
21+
"labels": ["conductor:task", "enhancement", "frontend"],
22+
},
23+
{
24+
"title": "Configure ESLint and Prettier",
25+
"labels": ["conductor:task", "code-quality", "dev-experience"],
26+
},
27+
],
28+
},
29+
"python-fastapi": {
30+
"patterns": ["fastapi", "python", "uvicorn", "pydantic"],
31+
"roles": {"default": "dev", "specialized": ["backend", "code-reviewer"]},
32+
"github_integration": {"issue_to_task": True, "pr_reviews": True},
33+
"build_validation": ["pytest", "black --check ."],
34+
"suggested_tasks": [
35+
{
36+
"title": "Add API documentation with OpenAPI",
37+
"labels": ["conductor:task", "documentation", "backend"],
38+
},
39+
{
40+
"title": "Set up database migrations with Alembic",
41+
"labels": ["conductor:task", "database", "backend"],
42+
},
43+
{
44+
"title": "Add integration tests for endpoints",
45+
"labels": ["conductor:task", "testing", "backend"],
46+
},
47+
],
48+
},
49+
"nextjs-fullstack": {
50+
"patterns": ["next", "react", "vercel"],
51+
"roles": {
52+
"default": "dev",
53+
"specialized": ["frontend", "backend", "code-reviewer"],
54+
},
55+
"github_integration": {"issue_to_task": True, "pr_reviews": True},
56+
"build_validation": ["npm test", "npm run build", "npm run lint"],
57+
"suggested_tasks": [
58+
{
59+
"title": "Set up authentication with NextAuth.js",
60+
"labels": ["conductor:task", "auth", "fullstack"],
61+
},
62+
{
63+
"title": "Configure Prisma for database access",
64+
"labels": ["conductor:task", "database", "backend"],
65+
},
66+
{
67+
"title": "Add E2E tests with Playwright",
68+
"labels": ["conductor:task", "testing", "e2e"],
69+
},
70+
],
71+
},
72+
"vue-javascript": {
73+
"patterns": ["vue", "nuxt", "vite"],
74+
"roles": {"default": "dev", "specialized": ["frontend", "code-reviewer"]},
75+
"github_integration": {"issue_to_task": True, "pr_reviews": True},
76+
"build_validation": ["npm test", "npm run build"],
77+
"suggested_tasks": [
78+
{
79+
"title": "Set up Pinia for state management",
80+
"labels": ["conductor:task", "state-management", "frontend"],
81+
},
82+
{
83+
"title": "Add component testing with Vitest",
84+
"labels": ["conductor:task", "testing", "frontend"],
85+
},
86+
{
87+
"title": "Configure Vue Router for navigation",
88+
"labels": ["conductor:task", "routing", "frontend"],
89+
},
90+
],
91+
},
92+
"python-django": {
93+
"patterns": ["django", "python", "wsgi"],
94+
"roles": {"default": "dev", "specialized": ["backend", "code-reviewer"]},
95+
"github_integration": {"issue_to_task": True, "pr_reviews": True},
96+
"build_validation": ["python manage.py test", "black --check ."],
97+
"suggested_tasks": [
98+
{
99+
"title": "Set up Django REST framework",
100+
"labels": ["conductor:task", "api", "backend"],
101+
},
102+
{
103+
"title": "Configure Celery for async tasks",
104+
"labels": ["conductor:task", "async", "backend"],
105+
},
106+
{
107+
"title": "Add Django Debug Toolbar",
108+
"labels": ["conductor:task", "dev-experience", "backend"],
109+
},
110+
],
111+
},
112+
"go-microservices": {
113+
"patterns": ["go", "gin", "fiber", "echo"],
114+
"roles": {"default": "dev", "specialized": ["backend", "code-reviewer"]},
115+
"github_integration": {"issue_to_task": True, "pr_reviews": True},
116+
"build_validation": ["go test ./...", "go build"],
117+
"suggested_tasks": [
118+
{
119+
"title": "Set up structured logging with zerolog",
120+
"labels": ["conductor:task", "observability", "backend"],
121+
},
122+
{
123+
"title": "Add OpenTelemetry instrumentation",
124+
"labels": ["conductor:task", "observability", "backend"],
125+
},
126+
{
127+
"title": "Create Dockerfile and docker-compose",
128+
"labels": ["conductor:task", "devops", "containers"],
129+
},
130+
],
131+
},
132+
"rust-cli": {
133+
"patterns": ["rust", "cargo", "clap"],
134+
"roles": {"default": "dev", "specialized": ["backend", "code-reviewer"]},
135+
"github_integration": {"issue_to_task": True, "pr_reviews": True},
136+
"build_validation": ["cargo test", "cargo build --release"],
137+
"suggested_tasks": [
138+
{
139+
"title": "Add comprehensive CLI tests",
140+
"labels": ["conductor:task", "testing", "cli"],
141+
},
142+
{
143+
"title": "Set up GitHub release workflow",
144+
"labels": ["conductor:task", "ci-cd", "devops"],
145+
},
146+
{
147+
"title": "Add shell completion generation",
148+
"labels": ["conductor:task", "enhancement", "cli"],
149+
},
150+
],
151+
},
152+
"mobile-react-native": {
153+
"patterns": ["react-native", "expo", "ios", "android"],
154+
"roles": {"default": "dev", "specialized": ["mobile", "code-reviewer"]},
155+
"github_integration": {"issue_to_task": True, "pr_reviews": True},
156+
"build_validation": ["npm test", "npm run lint"],
157+
"suggested_tasks": [
158+
{
159+
"title": "Set up Detox for E2E testing",
160+
"labels": ["conductor:task", "testing", "mobile"],
161+
},
162+
{
163+
"title": "Configure push notifications",
164+
"labels": ["conductor:task", "feature", "mobile"],
165+
},
166+
{
167+
"title": "Add crash reporting with Sentry",
168+
"labels": ["conductor:task", "monitoring", "mobile"],
169+
},
170+
],
171+
},
172+
}
173+
174+
175+
def get_express_config(stack_info: Dict[str, Any]) -> Optional[Dict[str, Any]]:
176+
"""Match detected stack to express config"""
177+
# Use the primary stack from summary if available
178+
if stack_info.get("summary", {}).get("primary_stack"):
179+
stack_name = stack_info["summary"]["primary_stack"]
180+
if stack_name in EXPRESS_CONFIGS:
181+
return EXPRESS_CONFIGS[stack_name]
182+
183+
# Otherwise try pattern matching
184+
detected_items = set()
185+
detected_items.update(stack_info.get("frameworks", []))
186+
detected_items.update(stack_info.get("summary", {}).get("languages", []))
187+
detected_items.update(stack_info.get("summary", {}).get("tools", []))
188+
189+
# Add items from modern tools
190+
modern = stack_info.get("modern_tools", {})
191+
if modern.get("framework"):
192+
detected_items.add(modern["framework"])
193+
if modern.get("build_tool"):
194+
detected_items.add(modern["build_tool"])
195+
196+
# Find best match
197+
best_match = None
198+
best_score = 0
199+
200+
for stack_name, config in EXPRESS_CONFIGS.items():
201+
score = len(detected_items.intersection(config["patterns"]))
202+
if score > best_score:
203+
best_match = stack_name
204+
best_score = score
205+
206+
return EXPRESS_CONFIGS.get(best_match) if best_match and best_score > 0 else None
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
Interactive configuration gathering
3+
"""
4+
5+
from pathlib import Path
6+
from typing import Dict, Any, List, Optional
7+
8+
9+
class InteractiveConfigurator:
10+
"""Handles interactive configuration prompts"""
11+
12+
def __init__(self, project_root: Path, debug: bool = False):
13+
self.project_root = project_root
14+
self.debug = debug
15+
self.config = {}
16+
17+
def configure(self, detected_stack: List[Dict[str, Any]]) -> Dict[str, Any]:
18+
"""Run interactive configuration"""
19+
print("\n" + "=" * 60)
20+
print("🎼 Code Conductor Interactive Setup")
21+
print("=" * 60)
22+
23+
# Basic configuration
24+
self.config["project_name"] = self._safe_input(
25+
"\n📝 Project name", default=self.project_root.name
26+
)
27+
28+
# Documentation
29+
self.config["docs_directory"] = self._get_docs_directory()
30+
31+
# Agent roles
32+
self._configure_roles(detected_stack)
33+
34+
# Task management
35+
self._configure_task_management()
36+
37+
# Concurrent agents
38+
self._configure_concurrent_agents()
39+
40+
return self.config
41+
42+
def _safe_input(self, prompt: str, default: Optional[str] = None) -> str:
43+
"""Get user input with a default value"""
44+
if default:
45+
prompt = f"{prompt} [{default}]"
46+
prompt += ": "
47+
48+
value = input(prompt).strip()
49+
if not value and default:
50+
return default
51+
return value
52+
53+
def _get_docs_directory(self) -> str:
54+
"""Ask for documentation directory"""
55+
docs_default = self._find_docs_directory()
56+
docs_prompt = "\n📚 Documentation directory"
57+
if docs_default:
58+
return self._safe_input(docs_prompt, default=docs_default)
59+
else:
60+
docs_dir = self._safe_input(docs_prompt + " (or 'none')", default="docs")
61+
return docs_dir if docs_dir.lower() != "none" else ""
62+
63+
def _find_docs_directory(self) -> str:
64+
"""Auto-detect documentation directory"""
65+
common_docs_dirs = ["docs", "documentation", "doc", "README.md"]
66+
for doc_dir in common_docs_dirs:
67+
if (self.project_root / doc_dir).exists():
68+
return doc_dir
69+
return ""
70+
71+
def _configure_roles(self, detected_stack: List[Dict[str, Any]]):
72+
"""Configure agent roles based on detected stack"""
73+
print("\n🤖 Agent Roles Configuration")
74+
print("-" * 40)
75+
76+
# Suggest roles based on detected stack
77+
suggested_roles = ["dev", "code-reviewer"] # Always include these
78+
stack_flat = []
79+
for item in detected_stack:
80+
if isinstance(item, dict):
81+
stack_flat.extend(item.values())
82+
else:
83+
stack_flat.append(item)
84+
85+
# Add specialized roles based on detection
86+
if any("react" in str(tech).lower() for tech in stack_flat):
87+
suggested_roles.append("frontend")
88+
if any(
89+
tech in str(stack_flat).lower()
90+
for tech in ["django", "flask", "fastapi", "express"]
91+
):
92+
suggested_roles.append("backend")
93+
if any("mobile" in str(tech).lower() for tech in stack_flat):
94+
suggested_roles.append("mobile")
95+
96+
print(f"Suggested roles: {', '.join(suggested_roles)}")
97+
custom_roles = self._safe_input(
98+
"Additional roles (comma-separated) or Enter to accept", default=""
99+
)
100+
101+
if custom_roles:
102+
additional = [r.strip() for r in custom_roles.split(",") if r.strip()]
103+
suggested_roles.extend(additional)
104+
105+
# Remove duplicates while preserving order
106+
seen = set()
107+
unique_roles = []
108+
for role in suggested_roles:
109+
if role not in seen:
110+
seen.add(role)
111+
unique_roles.append(role)
112+
113+
self.config["roles"] = {
114+
"default": "dev",
115+
"specialized": [r for r in unique_roles if r != "dev"],
116+
}
117+
118+
def _configure_task_management(self):
119+
"""Configure task management preferences"""
120+
print("\n📋 Task Management")
121+
print("-" * 40)
122+
print("How should tasks be managed?")
123+
print("1. GitHub Issues (recommended)")
124+
print("2. Local task files only")
125+
126+
choice = self._safe_input("Choice [1]", default="1")
127+
if choice == "2":
128+
self.config["github_integration"] = {
129+
"issue_to_task": False,
130+
"pr_reviews": False,
131+
}
132+
self.config["task_management"] = "local"
133+
else:
134+
self.config["github_integration"] = {
135+
"issue_to_task": True,
136+
"pr_reviews": True,
137+
}
138+
self.config["task_management"] = "github-issues"
139+
140+
def _configure_concurrent_agents(self):
141+
"""Configure concurrent agent limits"""
142+
default_agents = "5"
143+
agent_count = self._safe_input(
144+
"\n🔀 Maximum concurrent agents", default=default_agents
145+
)
146+
147+
try:
148+
self.config["max_concurrent_agents"] = int(agent_count)
149+
except ValueError:
150+
print(f"Invalid number, using default: {default_agents}")
151+
self.config["max_concurrent_agents"] = int(default_agents)

0 commit comments

Comments
 (0)