mdan-cli 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,558 @@
1
+ """CrewAI Orchestrator - Main orchestrator for CrewAI integration
2
+
3
+ Intelligent orchestrator that decides which agent/skill to call based on task analysis.
4
+ Supports auto-mode management, skill routing, and multi-agent debates.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Dict, Any, Optional, List, Union
12
+ from enum import Enum
13
+
14
+ from crewai import Crew, Process, Agent, Task
15
+
16
+ from .agents.product_agent import ProductAgent
17
+ from .agents.architect_agent import ArchitectAgent
18
+ from .agents.ux_agent import UXAgent
19
+ from .agents.dev_agent import DevAgent
20
+ from .agents.test_agent import TestAgent
21
+ from .agents.security_agent import SecurityAgent
22
+ from .agents.devops_agent import DevOpsAgent
23
+ from .agents.doc_agent import DocAgent
24
+
25
+ from .tools.sql_tool import SQLTool
26
+ from .tools.serper_tool import SerperTool
27
+ from .tools.file_tool import FileTool
28
+
29
+ from .flows.auto_flow import AutoFlow
30
+ from .flows.discovery_flow import DiscoveryFlow
31
+ from .flows.build_flow import BuildFlow
32
+ from .flows.debate_flow import DebateFlow
33
+
34
+
35
+ class Phase(Enum):
36
+ """MDAN development phases."""
37
+
38
+ LOAD = "LOAD"
39
+ DISCOVER = "DISCOVER"
40
+ PLAN = "PLAN"
41
+ ARCHITECT = "ARCHITECT"
42
+ IMPLEMENT = "IMPLEMENT"
43
+ TEST = "TEST"
44
+ DEPLOY = "DEPLOY"
45
+ DOC = "DOC"
46
+
47
+
48
+ class CrewAIOrchestrator:
49
+ """Main CrewAI orchestrator for MDAN.
50
+
51
+ Provides intelligent task routing, agent selection, and auto-mode management.
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ project_path: str,
57
+ llm=None,
58
+ sql_config: Optional[Dict[str, Any]] = None,
59
+ serper_api_key: Optional[str] = None,
60
+ auto_mode: bool = False,
61
+ ):
62
+ """Initialize CrewAI Orchestrator.
63
+
64
+ Args:
65
+ project_path: Path to the project directory
66
+ llm: Language model instance
67
+ sql_config: SQL database configuration
68
+ serper_api_key: Serper API key for web search
69
+ auto_mode: Enable autonomous mode
70
+ """
71
+ self.project_path = Path(project_path)
72
+ self.llm = llm
73
+ self.auto_mode = auto_mode
74
+
75
+ # Initialize tools
76
+ self.sql_tool = SQLTool(**sql_config) if sql_config else None
77
+ self.serper_tool = (
78
+ SerperTool(api_key=serper_api_key) if serper_api_key else None
79
+ )
80
+ self.file_tool = FileTool(base_path=project_path)
81
+
82
+ # Initialize agents
83
+ self.agents = self._initialize_agents()
84
+
85
+ # Initialize flows
86
+ self.flows = self._initialize_flows()
87
+
88
+ # Orchestrator state
89
+ self.state = {
90
+ "current_phase": None,
91
+ "active_agents": [],
92
+ "task_history": [],
93
+ "auto_mode_enabled": auto_mode,
94
+ }
95
+
96
+ def _initialize_agents(self) -> Dict[str, Any]:
97
+ """Initialize all CrewAI agents.
98
+
99
+ Returns:
100
+ Dictionary of agent instances
101
+ """
102
+ return {
103
+ "product": ProductAgent(
104
+ sql_tool=self.sql_tool,
105
+ serper_tool=self.serper_tool,
106
+ file_tool=self.file_tool,
107
+ llm=self.llm,
108
+ ),
109
+ "architect": ArchitectAgent(
110
+ sql_tool=self.sql_tool,
111
+ serper_tool=self.serper_tool,
112
+ file_tool=self.file_tool,
113
+ llm=self.llm,
114
+ ),
115
+ "ux": UXAgent(
116
+ sql_tool=self.sql_tool,
117
+ serper_tool=self.serper_tool,
118
+ file_tool=self.file_tool,
119
+ llm=self.llm,
120
+ ),
121
+ "dev": DevAgent(
122
+ sql_tool=self.sql_tool,
123
+ serper_tool=self.serper_tool,
124
+ file_tool=self.file_tool,
125
+ llm=self.llm,
126
+ ),
127
+ "test": TestAgent(
128
+ sql_tool=self.sql_tool,
129
+ serper_tool=self.serper_tool,
130
+ file_tool=self.file_tool,
131
+ llm=self.llm,
132
+ ),
133
+ "security": SecurityAgent(
134
+ sql_tool=self.sql_tool,
135
+ serper_tool=self.serper_tool,
136
+ file_tool=self.file_tool,
137
+ llm=self.llm,
138
+ ),
139
+ "devops": DevOpsAgent(
140
+ sql_tool=self.sql_tool,
141
+ serper_tool=self.serper_tool,
142
+ file_tool=self.file_tool,
143
+ llm=self.llm,
144
+ ),
145
+ "doc": DocAgent(
146
+ sql_tool=self.sql_tool,
147
+ serper_tool=self.serper_tool,
148
+ file_tool=self.file_tool,
149
+ llm=self.llm,
150
+ ),
151
+ }
152
+
153
+ def _initialize_flows(self) -> Dict[str, Any]:
154
+ """Initialize all CrewAI flows.
155
+
156
+ Returns:
157
+ Dictionary of flow instances
158
+ """
159
+ return {
160
+ "auto": AutoFlow(
161
+ project_path=str(self.project_path),
162
+ llm=self.llm,
163
+ sql_config=self.sql_tool.config if self.sql_tool else None,
164
+ serper_api_key=os.getenv("SERPER_API_KEY"),
165
+ ),
166
+ "discovery": DiscoveryFlow(
167
+ project_path=str(self.project_path),
168
+ llm=self.llm,
169
+ sql_config=self.sql_tool.config if self.sql_tool else None,
170
+ serper_api_key=os.getenv("SERPER_API_KEY"),
171
+ ),
172
+ "build": BuildFlow(
173
+ project_path=str(self.project_path),
174
+ llm=self.llm,
175
+ sql_config=self.sql_tool.config if self.sql_tool else None,
176
+ serper_api_key=os.getenv("SERPER_API_KEY"),
177
+ ),
178
+ "debate": DebateFlow(
179
+ project_path=str(self.project_path),
180
+ llm=self.llm,
181
+ sql_config=self.sql_tool.config if self.sql_tool else None,
182
+ serper_api_key=os.getenv("SERPER_API_KEY"),
183
+ ),
184
+ }
185
+
186
+ def analyze_task(self, task_description: str) -> Dict[str, Any]:
187
+ """Analyze task and determine appropriate agent/flow.
188
+
189
+ Args:
190
+ task_description: Description of the task
191
+
192
+ Returns:
193
+ Analysis result with recommended agent/flow
194
+ """
195
+ task_lower = task_description.lower()
196
+
197
+ # Check for flow-based tasks
198
+ if any(
199
+ keyword in task_lower
200
+ for keyword in ["auto", "autonomous", "full cycle", "complete"]
201
+ ):
202
+ return {
203
+ "type": "flow",
204
+ "flow": "auto",
205
+ "reason": "Task requires autonomous full-cycle development",
206
+ }
207
+
208
+ if any(
209
+ keyword in task_lower
210
+ for keyword in ["discover", "requirement", "prd", "user story"]
211
+ ):
212
+ return {
213
+ "type": "flow",
214
+ "flow": "discovery",
215
+ "reason": "Task requires discovery phase execution",
216
+ }
217
+
218
+ if any(
219
+ keyword in task_lower
220
+ for keyword in ["build", "implement", "develop", "code"]
221
+ ):
222
+ return {
223
+ "type": "flow",
224
+ "flow": "build",
225
+ "reason": "Task requires build phase execution",
226
+ }
227
+
228
+ if any(
229
+ keyword in task_lower
230
+ for keyword in ["debate", "consensus", "discuss", "decide"]
231
+ ):
232
+ return {
233
+ "type": "flow",
234
+ "flow": "debate",
235
+ "reason": "Task requires multi-agent debate",
236
+ }
237
+
238
+ # Check for agent-specific tasks
239
+ if any(
240
+ keyword in task_lower
241
+ for keyword in ["prd", "requirement", "user story", "persona", "feature"]
242
+ ):
243
+ return {
244
+ "type": "agent",
245
+ "agent": "product",
246
+ "reason": "Task requires product management expertise",
247
+ }
248
+
249
+ if any(
250
+ keyword in task_lower
251
+ for keyword in ["architecture", "tech stack", "design", "api", "schema"]
252
+ ):
253
+ return {
254
+ "type": "agent",
255
+ "agent": "architect",
256
+ "reason": "Task requires architecture expertise",
257
+ }
258
+
259
+ if any(
260
+ keyword in task_lower
261
+ for keyword in ["ux", "ui", "user flow", "wireframe", "design system"]
262
+ ):
263
+ return {
264
+ "type": "agent",
265
+ "agent": "ux",
266
+ "reason": "Task requires UX design expertise",
267
+ }
268
+
269
+ if any(
270
+ keyword in task_lower
271
+ for keyword in ["implement", "code", "refactor", "debug", "review"]
272
+ ):
273
+ return {
274
+ "type": "agent",
275
+ "agent": "dev",
276
+ "reason": "Task requires development expertise",
277
+ }
278
+
279
+ if any(
280
+ keyword in task_lower
281
+ for keyword in ["test", "quality", "verify", "coverage"]
282
+ ):
283
+ return {
284
+ "type": "agent",
285
+ "agent": "test",
286
+ "reason": "Task requires testing expertise",
287
+ }
288
+
289
+ if any(
290
+ keyword in task_lower
291
+ for keyword in ["security", "vulnerability", "auth", "encrypt"]
292
+ ):
293
+ return {
294
+ "type": "agent",
295
+ "agent": "security",
296
+ "reason": "Task requires security expertise",
297
+ }
298
+
299
+ if any(
300
+ keyword in task_lower
301
+ for keyword in ["deploy", "ci/cd", "infrastructure", "cloud", "azure"]
302
+ ):
303
+ return {
304
+ "type": "agent",
305
+ "agent": "devops",
306
+ "reason": "Task requires DevOps expertise",
307
+ }
308
+
309
+ if any(
310
+ keyword in task_lower
311
+ for keyword in ["document", "guide", "readme", "api doc"]
312
+ ):
313
+ return {
314
+ "type": "agent",
315
+ "agent": "doc",
316
+ "reason": "Task requires documentation expertise",
317
+ }
318
+
319
+ # Default to product agent for general tasks
320
+ return {
321
+ "type": "agent",
322
+ "agent": "product",
323
+ "reason": "Default agent for general tasks",
324
+ }
325
+
326
+ async def execute_task(
327
+ self, task_description: str, context: Optional[Dict[str, Any]] = None
328
+ ) -> Dict[str, Any]:
329
+ """Execute a task using appropriate agent/flow.
330
+
331
+ Args:
332
+ task_description: Description of the task
333
+ context: Additional context for the task
334
+
335
+ Returns:
336
+ Task execution result
337
+ """
338
+ # Analyze task
339
+ analysis = self.analyze_task(task_description)
340
+
341
+ # Log task
342
+ self.state["task_history"].append(
343
+ {
344
+ "description": task_description,
345
+ "analysis": analysis,
346
+ "context": context,
347
+ "timestamp": str(asyncio.get_event_loop().time()),
348
+ }
349
+ )
350
+
351
+ # Execute based on analysis
352
+ if analysis["type"] == "flow":
353
+ return await self._execute_flow(analysis["flow"], task_description, context)
354
+ else:
355
+ return await self._execute_agent_task(
356
+ analysis["agent"], task_description, context
357
+ )
358
+
359
+ async def _execute_flow(
360
+ self,
361
+ flow_name: str,
362
+ task_description: str,
363
+ context: Optional[Dict[str, Any]] = None,
364
+ ) -> Dict[str, Any]:
365
+ """Execute a CrewAI flow.
366
+
367
+ Args:
368
+ flow_name: Name of the flow to execute
369
+ task_description: Description of the task
370
+ context: Additional context
371
+
372
+ Returns:
373
+ Flow execution result
374
+ """
375
+ flow = self.flows.get(flow_name)
376
+ if not flow:
377
+ return {"status": "error", "error": f"Flow '{flow_name}' not found"}
378
+
379
+ try:
380
+ if flow_name == "auto":
381
+ result = await flow.kickoff()
382
+ elif flow_name == "discovery":
383
+ result = await flow.kickoff(task_description)
384
+ elif flow_name == "build":
385
+ result = await flow.kickoff(
386
+ str(context) if context else task_description
387
+ )
388
+ elif flow_name == "debate":
389
+ result = await flow.kickoff(task_description)
390
+ else:
391
+ result = await flow.kickoff()
392
+
393
+ return {"status": "success", "flow": flow_name, "result": result}
394
+ except Exception as e:
395
+ return {"status": "error", "flow": flow_name, "error": str(e)}
396
+
397
+ async def _execute_agent_task(
398
+ self,
399
+ agent_name: str,
400
+ task_description: str,
401
+ context: Optional[Dict[str, Any]] = None,
402
+ ) -> Dict[str, Any]:
403
+ """Execute a task with a specific agent.
404
+
405
+ Args:
406
+ agent_name: Name of the agent
407
+ task_description: Description of the task
408
+ context: Additional context
409
+
410
+ Returns:
411
+ Task execution result
412
+ """
413
+ agent_wrapper = self.agents.get(agent_name)
414
+ if not agent_wrapper:
415
+ return {"status": "error", "error": f"Agent '{agent_name}' not found"}
416
+
417
+ try:
418
+ # Create task
419
+ task = Task(
420
+ description=task_description,
421
+ agent=agent_wrapper.get_agent(),
422
+ expected_output="Task completion result",
423
+ )
424
+
425
+ # Execute task
426
+ result = await asyncio.to_thread(task.execute)
427
+
428
+ return {"status": "success", "agent": agent_name, "result": result}
429
+ except Exception as e:
430
+ return {"status": "error", "agent": agent_name, "error": str(e)}
431
+
432
+ async def run_auto_mode(self, user_input: str) -> Dict[str, Any]:
433
+ """Run autonomous mode.
434
+
435
+ Args:
436
+ user_input: Initial user input for the project
437
+
438
+ Returns:
439
+ Auto mode execution result
440
+ """
441
+ if not self.auto_mode:
442
+ return {"status": "error", "error": "Auto mode is not enabled"}
443
+
444
+ print("🚀 Starting autonomous mode...")
445
+
446
+ try:
447
+ # Execute auto flow
448
+ result = await self.flows["auto"].kickoff()
449
+
450
+ return {"status": "success", "result": result}
451
+ except Exception as e:
452
+ return {"status": "error", "error": str(e)}
453
+
454
+ async def start_debate(self, topic: str) -> Dict[str, Any]:
455
+ """Start a multi-agent debate.
456
+
457
+ Args:
458
+ topic: Topic to debate
459
+
460
+ Returns:
461
+ Debate result
462
+ """
463
+ print(f"🎯 Starting debate on: {topic}")
464
+
465
+ try:
466
+ result = await self.flows["debate"].kickoff(topic)
467
+
468
+ return {"status": "success", "result": result}
469
+ except Exception as e:
470
+ return {"status": "error", "error": str(e)}
471
+
472
+ def create_crew(
473
+ self,
474
+ agent_names: List[str],
475
+ process: Process = Process.sequential,
476
+ verbose: bool = True,
477
+ ) -> Crew:
478
+ """Create a Crew with specified agents.
479
+
480
+ Args:
481
+ agent_names: List of agent names to include
482
+ process: Crew process type (sequential or hierarchical)
483
+ verbose: Enable verbose output
484
+
485
+ Returns:
486
+ Crew instance
487
+ """
488
+ agents = []
489
+ for name in agent_names:
490
+ agent_wrapper = self.agents.get(name)
491
+ if agent_wrapper:
492
+ agents.append(agent_wrapper.get_agent())
493
+
494
+ if not agents:
495
+ raise ValueError(f"No valid agents found in {agent_names}")
496
+
497
+ return Crew(agents=agents, process=process, verbose=verbose)
498
+
499
+ async def execute_crew(self, crew: Crew, tasks: List[Task]) -> Dict[str, Any]:
500
+ """Execute a Crew with tasks.
501
+
502
+ Args:
503
+ crew: Crew instance
504
+ tasks: List of tasks to execute
505
+
506
+ Returns:
507
+ Crew execution result
508
+ """
509
+ try:
510
+ result = await asyncio.to_thread(crew.kickoff, tasks)
511
+
512
+ return {"status": "success", "result": result}
513
+ except Exception as e:
514
+ return {"status": "error", "error": str(e)}
515
+
516
+ def get_state(self) -> Dict[str, Any]:
517
+ """Get orchestrator state.
518
+
519
+ Returns:
520
+ Current orchestrator state
521
+ """
522
+ return self.state
523
+
524
+ def save_state(self, filepath: str):
525
+ """Save orchestrator state to file.
526
+
527
+ Args:
528
+ filepath: Path to save state
529
+ """
530
+ with open(filepath, "w") as f:
531
+ json.dump(self.state, f, indent=2)
532
+
533
+ def load_state(self, filepath: str):
534
+ """Load orchestrator state from file.
535
+
536
+ Args:
537
+ filepath: Path to load state from
538
+ """
539
+ with open(filepath, "r") as f:
540
+ self.state = json.load(f)
541
+
542
+ def enable_auto_mode(self):
543
+ """Enable autonomous mode."""
544
+ self.auto_mode = True
545
+ self.state["auto_mode_enabled"] = True
546
+
547
+ def disable_auto_mode(self):
548
+ """Disable autonomous mode."""
549
+ self.auto_mode = False
550
+ self.state["auto_mode_enabled"] = False
551
+
552
+ def is_auto_mode_enabled(self) -> bool:
553
+ """Check if auto mode is enabled.
554
+
555
+ Returns:
556
+ True if auto mode is enabled
557
+ """
558
+ return self.auto_mode
@@ -0,0 +1,8 @@
1
+ """CrewAI Skills Package"""
2
+
3
+ from .skill_router import SkillRouter, Skill
4
+
5
+ __all__ = [
6
+ "SkillRouter",
7
+ "Skill",
8
+ ]