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,451 @@
1
+ """Auto Flow - Main autonomous flow with 8 phases
2
+
3
+ Orchestrates the complete autonomous development cycle:
4
+ LOAD → DISCOVER → PLAN → ARCHITECT → IMPLEMENT → TEST → DEPLOY → DOC
5
+ """
6
+
7
+ from crewai.flow import Flow, listen, start
8
+ from typing import Dict, Any, Optional
9
+ import asyncio
10
+ import json
11
+ from pathlib import Path
12
+
13
+ from ..agents.product_agent import ProductAgent
14
+ from ..agents.architect_agent import ArchitectAgent
15
+ from ..agents.ux_agent import UXAgent
16
+ from ..agents.dev_agent import DevAgent
17
+ from ..agents.test_agent import TestAgent
18
+ from ..agents.security_agent import SecurityAgent
19
+ from ..agents.devops_agent import DevOpsAgent
20
+ from ..agents.doc_agent import DocAgent
21
+
22
+ from ..tools.sql_tool import SQLTool
23
+ from ..tools.serper_tool import SerperTool
24
+ from ..tools.file_tool import FileTool
25
+
26
+
27
+ class AutoFlow(Flow):
28
+ """Main autonomous flow for full-cycle development."""
29
+
30
+ def __init__(
31
+ self,
32
+ project_path: str,
33
+ llm=None,
34
+ sql_config: Optional[Dict[str, Any]] = None,
35
+ serper_api_key: Optional[str] = None,
36
+ ):
37
+ """Initialize Auto Flow.
38
+
39
+ Args:
40
+ project_path: Path to the project directory
41
+ llm: Language model instance
42
+ sql_config: SQL database configuration
43
+ serper_api_key: Serper API key for web search
44
+ """
45
+ super().__init__()
46
+ self.project_path = Path(project_path)
47
+ self.llm = llm
48
+
49
+ # Initialize tools
50
+ self.sql_tool = SQLTool(**sql_config) if sql_config else None
51
+ self.serper_tool = (
52
+ SerperTool(api_key=serper_api_key) if serper_api_key else None
53
+ )
54
+ self.file_tool = FileTool(base_path=project_path)
55
+
56
+ # Initialize agents
57
+ self.product_agent = ProductAgent(
58
+ sql_tool=self.sql_tool,
59
+ serper_tool=self.serper_tool,
60
+ file_tool=self.file_tool,
61
+ llm=llm,
62
+ )
63
+
64
+ self.architect_agent = ArchitectAgent(
65
+ sql_tool=self.sql_tool,
66
+ serper_tool=self.serper_tool,
67
+ file_tool=self.file_tool,
68
+ llm=llm,
69
+ )
70
+
71
+ self.ux_agent = UXAgent(
72
+ sql_tool=self.sql_tool,
73
+ serper_tool=self.serper_tool,
74
+ file_tool=self.file_tool,
75
+ llm=llm,
76
+ )
77
+
78
+ self.dev_agent = DevAgent(
79
+ sql_tool=self.sql_tool,
80
+ serper_tool=self.serper_tool,
81
+ file_tool=self.file_tool,
82
+ llm=llm,
83
+ )
84
+
85
+ self.test_agent = TestAgent(
86
+ sql_tool=self.sql_tool,
87
+ serper_tool=self.serper_tool,
88
+ file_tool=self.file_tool,
89
+ llm=llm,
90
+ )
91
+
92
+ self.security_agent = SecurityAgent(
93
+ sql_tool=self.sql_tool,
94
+ serper_tool=self.serper_tool,
95
+ file_tool=self.file_tool,
96
+ llm=llm,
97
+ )
98
+
99
+ self.devops_agent = DevOpsAgent(
100
+ sql_tool=self.sql_tool,
101
+ serper_tool=self.serper_tool,
102
+ file_tool=self.file_tool,
103
+ llm=llm,
104
+ )
105
+
106
+ self.doc_agent = DocAgent(
107
+ sql_tool=self.sql_tool,
108
+ serper_tool=self.serper_tool,
109
+ file_tool=self.file_tool,
110
+ llm=llm,
111
+ )
112
+
113
+ # Flow state
114
+ self.state = {
115
+ "current_phase": None,
116
+ "project_context": {},
117
+ "phase_results": {},
118
+ "errors": [],
119
+ }
120
+
121
+ @start
122
+ async def load_phase(self):
123
+ """LOAD phase: Load project context and state."""
124
+ self.state["current_phase"] = "LOAD"
125
+
126
+ print("🔄 LOAD Phase: Loading project context...")
127
+
128
+ # Load project state if exists
129
+ state_file = self.project_path / "MDAN-STATE.json"
130
+ if state_file.exists():
131
+ with open(state_file, "r") as f:
132
+ self.state["project_context"] = json.load(f)
133
+ print("✅ Loaded existing project state")
134
+ else:
135
+ self.state["project_context"] = {
136
+ "project_name": self.project_path.name,
137
+ "phases_completed": [],
138
+ "current_phase": "LOAD",
139
+ }
140
+ print("✅ Initialized new project state")
141
+
142
+ self.state["phase_results"]["LOAD"] = {
143
+ "status": "completed",
144
+ "context": self.state["project_context"],
145
+ }
146
+
147
+ return self.state["project_context"]
148
+
149
+ @listen(load_phase)
150
+ async def discover_phase(self, project_context: Dict[str, Any]):
151
+ """DISCOVER phase: Requirements gathering and PRD creation."""
152
+ self.state["current_phase"] = "DISCOVER"
153
+
154
+ print("🔍 DISCOVER Phase: Gathering requirements...")
155
+
156
+ # Create DISCOVER tasks
157
+ tasks = [
158
+ self.product_agent.create_prd_task(str(project_context)),
159
+ self.product_agent.create_user_stories_task(str(project_context)),
160
+ self.product_agent.create_personas_task(str(project_context)),
161
+ self.product_agent.create_feature_prioritization_task(str(project_context)),
162
+ ]
163
+
164
+ # Execute tasks
165
+ results = []
166
+ for task in tasks:
167
+ try:
168
+ result = await asyncio.to_thread(task.execute)
169
+ results.append(result)
170
+ print(f"✅ Completed: {task.description[:50]}...")
171
+ except Exception as e:
172
+ self.state["errors"].append(f"DISCOVER error: {str(e)}")
173
+ print(f"❌ Error in task: {str(e)}")
174
+
175
+ self.state["phase_results"]["DISCOVER"] = {
176
+ "status": "completed",
177
+ "results": results,
178
+ }
179
+
180
+ return results
181
+
182
+ @listen(discover_phase)
183
+ async def plan_phase(self, discover_results: list):
184
+ """PLAN phase: Project planning and task breakdown."""
185
+ self.state["current_phase"] = "PLAN"
186
+
187
+ print("📋 PLAN Phase: Creating project plan...")
188
+
189
+ # Create planning tasks
190
+ tasks = [
191
+ self.product_agent.create_acceptance_criteria_task(str(discover_results)),
192
+ self.product_agent.create_project_timeline_task(str(discover_results)),
193
+ ]
194
+
195
+ # Execute tasks
196
+ results = []
197
+ for task in tasks:
198
+ try:
199
+ result = await asyncio.to_thread(task.execute)
200
+ results.append(result)
201
+ print(f"✅ Completed: {task.description[:50]}...")
202
+ except Exception as e:
203
+ self.state["errors"].append(f"PLAN error: {str(e)}")
204
+ print(f"❌ Error in task: {str(e)}")
205
+
206
+ self.state["phase_results"]["PLAN"] = {
207
+ "status": "completed",
208
+ "results": results,
209
+ }
210
+
211
+ return results
212
+
213
+ @listen(plan_phase)
214
+ async def architect_phase(self, plan_results: list):
215
+ """ARCHITECT phase: System architecture and design."""
216
+ self.state["current_phase"] = "ARCHITECT"
217
+
218
+ print("🏗️ ARCHITECT Phase: Designing system architecture...")
219
+
220
+ # Create architecture tasks
221
+ tasks = [
222
+ self.architect_agent.create_architecture_task(str(plan_results)),
223
+ self.architect_agent.create_tech_stack_task(str(plan_results)),
224
+ self.architect_agent.create_adr_task(str(plan_results)),
225
+ self.architect_agent.create_api_design_task(str(plan_results)),
226
+ self.architect_agent.create_database_schema_task(str(plan_results)),
227
+ ]
228
+
229
+ # Execute tasks
230
+ results = []
231
+ for task in tasks:
232
+ try:
233
+ result = await asyncio.to_thread(task.execute)
234
+ results.append(result)
235
+ print(f"✅ Completed: {task.description[:50]}...")
236
+ except Exception as e:
237
+ self.state["errors"].append(f"ARCHITECT error: {str(e)}")
238
+ print(f"❌ Error in task: {str(e)}")
239
+
240
+ self.state["phase_results"]["ARCHITECT"] = {
241
+ "status": "completed",
242
+ "results": results,
243
+ }
244
+
245
+ return results
246
+
247
+ @listen(architect_phase)
248
+ async def implement_phase(self, architect_results: list):
249
+ """IMPLEMENT phase: Code implementation."""
250
+ self.state["current_phase"] = "IMPLEMENT"
251
+
252
+ print("💻 IMPLEMENT Phase: Implementing features...")
253
+
254
+ # Create implementation tasks
255
+ tasks = [
256
+ self.dev_agent.create_implementation_task(str(architect_results)),
257
+ self.dev_agent.create_refactoring_task(str(architect_results)),
258
+ self.dev_agent.create_code_review_task(str(architect_results)),
259
+ self.dev_agent.create_testing_task(str(architect_results)),
260
+ self.dev_agent.create_debugging_task(str(architect_results)),
261
+ ]
262
+
263
+ # Execute tasks
264
+ results = []
265
+ for task in tasks:
266
+ try:
267
+ result = await asyncio.to_thread(task.execute)
268
+ results.append(result)
269
+ print(f"✅ Completed: {task.description[:50]}...")
270
+ except Exception as e:
271
+ self.state["errors"].append(f"IMPLEMENT error: {str(e)}")
272
+ print(f"❌ Error in task: {str(e)}")
273
+
274
+ self.state["phase_results"]["IMPLEMENT"] = {
275
+ "status": "completed",
276
+ "results": results,
277
+ }
278
+
279
+ return results
280
+
281
+ @listen(implement_phase)
282
+ async def test_phase(self, implement_results: list):
283
+ """TEST phase: Testing and quality assurance."""
284
+ self.state["current_phase"] = "TEST"
285
+
286
+ print("🧪 TEST Phase: Running tests...")
287
+
288
+ # Create testing tasks
289
+ tasks = [
290
+ self.test_agent.create_test_strategy_task(str(implement_results)),
291
+ self.test_agent.create_unit_tests_task(str(implement_results)),
292
+ self.test_agent.create_integration_tests_task(str(implement_results)),
293
+ self.test_agent.create_e2e_tests_task(str(implement_results)),
294
+ self.test_agent.create_test_execution_task(str(implement_results)),
295
+ ]
296
+
297
+ # Execute tasks
298
+ results = []
299
+ for task in tasks:
300
+ try:
301
+ result = await asyncio.to_thread(task.execute)
302
+ results.append(result)
303
+ print(f"✅ Completed: {task.description[:50]}...")
304
+ except Exception as e:
305
+ self.state["errors"].append(f"TEST error: {str(e)}")
306
+ print(f"❌ Error in task: {str(e)}")
307
+
308
+ self.state["phase_results"]["TEST"] = {
309
+ "status": "completed",
310
+ "results": results,
311
+ }
312
+
313
+ return results
314
+
315
+ @listen(test_phase)
316
+ async def deploy_phase(self, test_results: list):
317
+ """DEPLOY phase: Deployment and infrastructure setup."""
318
+ self.state["current_phase"] = "DEPLOY"
319
+
320
+ print("🚀 DEPLOY Phase: Setting up deployment...")
321
+
322
+ # Create deployment tasks
323
+ tasks = [
324
+ self.devops_agent.create_ci_cd_pipeline_task(str(test_results)),
325
+ self.devops_agent.create_docker_setup_task(str(test_results)),
326
+ self.devops_agent.create_azure_deployment_task(str(test_results)),
327
+ self.devops_agent.create_monitoring_setup_task(str(test_results)),
328
+ self.devops_agent.create_deployment_strategy_task(str(test_results)),
329
+ ]
330
+
331
+ # Execute tasks
332
+ results = []
333
+ for task in tasks:
334
+ try:
335
+ result = await asyncio.to_thread(task.execute)
336
+ results.append(result)
337
+ print(f"✅ Completed: {task.description[:50]}...")
338
+ except Exception as e:
339
+ self.state["errors"].append(f"DEPLOY error: {str(e)}")
340
+ print(f"❌ Error in task: {str(e)}")
341
+
342
+ self.state["phase_results"]["DEPLOY"] = {
343
+ "status": "completed",
344
+ "results": results,
345
+ }
346
+
347
+ return results
348
+
349
+ @listen(deploy_phase)
350
+ async def doc_phase(self, deploy_results: list):
351
+ """DOC phase: Documentation creation."""
352
+ self.state["current_phase"] = "DOC"
353
+
354
+ print("📚 DOC Phase: Creating documentation...")
355
+
356
+ # Create documentation tasks
357
+ tasks = [
358
+ self.doc_agent.create_readme_task(str(deploy_results)),
359
+ self.doc_agent.create_api_documentation_task(str(deploy_results)),
360
+ self.doc_agent.create_user_guide_task(str(deploy_results)),
361
+ self.doc_agent.create_developer_guide_task(str(deploy_results)),
362
+ self.doc_agent.create_architecture_documentation_task(str(deploy_results)),
363
+ ]
364
+
365
+ # Execute tasks
366
+ results = []
367
+ for task in tasks:
368
+ try:
369
+ result = await asyncio.to_thread(task.execute)
370
+ results.append(result)
371
+ print(f"✅ Completed: {task.description[:50]}...")
372
+ except Exception as e:
373
+ self.state["errors"].append(f"DOC error: {str(e)}")
374
+ print(f"❌ Error in task: {str(e)}")
375
+
376
+ self.state["phase_results"]["DOC"] = {"status": "completed", "results": results}
377
+
378
+ return results
379
+
380
+ @listen(doc_phase)
381
+ async def finalize(self, doc_results: list):
382
+ """Finalize the autonomous flow."""
383
+ print("✨ Autonomous flow completed!")
384
+
385
+ # Update project state
386
+ self.state["project_context"]["phases_completed"] = [
387
+ "LOAD",
388
+ "DISCOVER",
389
+ "PLAN",
390
+ "ARCHITECT",
391
+ "IMPLEMENT",
392
+ "TEST",
393
+ "DEPLOY",
394
+ "DOC",
395
+ ]
396
+ self.state["project_context"]["current_phase"] = "COMPLETED"
397
+
398
+ # Save state
399
+ state_file = self.project_path / "MDAN-STATE.json"
400
+ with open(state_file, "w") as f:
401
+ json.dump(self.state["project_context"], f, indent=2)
402
+
403
+ print(f"✅ Project state saved to {state_file}")
404
+
405
+ if self.state["errors"]:
406
+ print(f"\n⚠️ Errors encountered: {len(self.state['errors'])}")
407
+ for error in self.state["errors"]:
408
+ print(f" - {error}")
409
+
410
+ return {
411
+ "status": "completed",
412
+ "phases": self.state["phase_results"],
413
+ "errors": self.state["errors"],
414
+ }
415
+
416
+ def get_state(self) -> Dict[str, Any]:
417
+ """Get current flow state.
418
+
419
+ Returns:
420
+ Current flow state
421
+ """
422
+ return self.state
423
+
424
+ def save_context(self, filepath: str):
425
+ """Save flow context to file.
426
+
427
+ Args:
428
+ filepath: Path to save context
429
+ """
430
+ context = {"state": self.state, "project_path": str(self.project_path)}
431
+ with open(filepath, "w") as f:
432
+ json.dump(context, f, indent=2)
433
+
434
+ @classmethod
435
+ def load_context(cls, filepath: str, llm=None) -> "AutoFlow":
436
+ """Load flow context from file.
437
+
438
+ Args:
439
+ filepath: Path to load context from
440
+ llm: Language model instance
441
+
442
+ Returns:
443
+ AutoFlow instance with loaded context
444
+ """
445
+ with open(filepath, "r") as f:
446
+ context = json.load(f)
447
+
448
+ flow = cls(project_path=context["project_path"], llm=llm)
449
+ flow.state = context["state"]
450
+
451
+ return flow