amd-gaia 0.15.0__py3-none-any.whl → 0.15.1__py3-none-any.whl

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.
Files changed (181) hide show
  1. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
  2. amd_gaia-0.15.1.dist-info/RECORD +178 -0
  3. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
  4. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/licenses/LICENSE.md +20 -20
  5. gaia/__init__.py +29 -29
  6. gaia/agents/__init__.py +19 -19
  7. gaia/agents/base/__init__.py +9 -9
  8. gaia/agents/base/agent.py +2177 -2177
  9. gaia/agents/base/api_agent.py +120 -120
  10. gaia/agents/base/console.py +1841 -1841
  11. gaia/agents/base/errors.py +237 -237
  12. gaia/agents/base/mcp_agent.py +86 -86
  13. gaia/agents/base/tools.py +83 -83
  14. gaia/agents/blender/agent.py +556 -556
  15. gaia/agents/blender/agent_simple.py +133 -135
  16. gaia/agents/blender/app.py +211 -211
  17. gaia/agents/blender/app_simple.py +41 -41
  18. gaia/agents/blender/core/__init__.py +16 -16
  19. gaia/agents/blender/core/materials.py +506 -506
  20. gaia/agents/blender/core/objects.py +316 -316
  21. gaia/agents/blender/core/rendering.py +225 -225
  22. gaia/agents/blender/core/scene.py +220 -220
  23. gaia/agents/blender/core/view.py +146 -146
  24. gaia/agents/chat/__init__.py +9 -9
  25. gaia/agents/chat/agent.py +835 -835
  26. gaia/agents/chat/app.py +1058 -1058
  27. gaia/agents/chat/session.py +508 -508
  28. gaia/agents/chat/tools/__init__.py +15 -15
  29. gaia/agents/chat/tools/file_tools.py +96 -96
  30. gaia/agents/chat/tools/rag_tools.py +1729 -1729
  31. gaia/agents/chat/tools/shell_tools.py +436 -436
  32. gaia/agents/code/__init__.py +7 -7
  33. gaia/agents/code/agent.py +549 -549
  34. gaia/agents/code/cli.py +377 -0
  35. gaia/agents/code/models.py +135 -135
  36. gaia/agents/code/orchestration/__init__.py +24 -24
  37. gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
  38. gaia/agents/code/orchestration/checklist_generator.py +713 -713
  39. gaia/agents/code/orchestration/factories/__init__.py +9 -9
  40. gaia/agents/code/orchestration/factories/base.py +63 -63
  41. gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
  42. gaia/agents/code/orchestration/factories/python_factory.py +106 -106
  43. gaia/agents/code/orchestration/orchestrator.py +841 -841
  44. gaia/agents/code/orchestration/project_analyzer.py +391 -391
  45. gaia/agents/code/orchestration/steps/__init__.py +67 -67
  46. gaia/agents/code/orchestration/steps/base.py +188 -188
  47. gaia/agents/code/orchestration/steps/error_handler.py +314 -314
  48. gaia/agents/code/orchestration/steps/nextjs.py +828 -828
  49. gaia/agents/code/orchestration/steps/python.py +307 -307
  50. gaia/agents/code/orchestration/template_catalog.py +469 -469
  51. gaia/agents/code/orchestration/workflows/__init__.py +14 -14
  52. gaia/agents/code/orchestration/workflows/base.py +80 -80
  53. gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
  54. gaia/agents/code/orchestration/workflows/python.py +94 -94
  55. gaia/agents/code/prompts/__init__.py +11 -11
  56. gaia/agents/code/prompts/base_prompt.py +77 -77
  57. gaia/agents/code/prompts/code_patterns.py +2036 -2036
  58. gaia/agents/code/prompts/nextjs_prompt.py +40 -40
  59. gaia/agents/code/prompts/python_prompt.py +109 -109
  60. gaia/agents/code/schema_inference.py +365 -365
  61. gaia/agents/code/system_prompt.py +41 -41
  62. gaia/agents/code/tools/__init__.py +42 -42
  63. gaia/agents/code/tools/cli_tools.py +1138 -1138
  64. gaia/agents/code/tools/code_formatting.py +319 -319
  65. gaia/agents/code/tools/code_tools.py +769 -769
  66. gaia/agents/code/tools/error_fixing.py +1347 -1347
  67. gaia/agents/code/tools/external_tools.py +180 -180
  68. gaia/agents/code/tools/file_io.py +845 -845
  69. gaia/agents/code/tools/prisma_tools.py +190 -190
  70. gaia/agents/code/tools/project_management.py +1016 -1016
  71. gaia/agents/code/tools/testing.py +321 -321
  72. gaia/agents/code/tools/typescript_tools.py +122 -122
  73. gaia/agents/code/tools/validation_parsing.py +461 -461
  74. gaia/agents/code/tools/validation_tools.py +806 -806
  75. gaia/agents/code/tools/web_dev_tools.py +1758 -1758
  76. gaia/agents/code/validators/__init__.py +16 -16
  77. gaia/agents/code/validators/antipattern_checker.py +241 -241
  78. gaia/agents/code/validators/ast_analyzer.py +197 -197
  79. gaia/agents/code/validators/requirements_validator.py +145 -145
  80. gaia/agents/code/validators/syntax_validator.py +171 -171
  81. gaia/agents/docker/__init__.py +7 -7
  82. gaia/agents/docker/agent.py +642 -642
  83. gaia/agents/emr/__init__.py +8 -8
  84. gaia/agents/emr/agent.py +1506 -1506
  85. gaia/agents/emr/cli.py +1322 -1322
  86. gaia/agents/emr/constants.py +475 -475
  87. gaia/agents/emr/dashboard/__init__.py +4 -4
  88. gaia/agents/emr/dashboard/server.py +1974 -1974
  89. gaia/agents/jira/__init__.py +11 -11
  90. gaia/agents/jira/agent.py +894 -894
  91. gaia/agents/jira/jql_templates.py +299 -299
  92. gaia/agents/routing/__init__.py +7 -7
  93. gaia/agents/routing/agent.py +567 -570
  94. gaia/agents/routing/system_prompt.py +75 -75
  95. gaia/agents/summarize/__init__.py +11 -0
  96. gaia/agents/summarize/agent.py +885 -0
  97. gaia/agents/summarize/prompts.py +129 -0
  98. gaia/api/__init__.py +23 -23
  99. gaia/api/agent_registry.py +238 -238
  100. gaia/api/app.py +305 -305
  101. gaia/api/openai_server.py +575 -575
  102. gaia/api/schemas.py +186 -186
  103. gaia/api/sse_handler.py +373 -373
  104. gaia/apps/__init__.py +4 -4
  105. gaia/apps/llm/__init__.py +6 -6
  106. gaia/apps/llm/app.py +173 -169
  107. gaia/apps/summarize/app.py +116 -633
  108. gaia/apps/summarize/html_viewer.py +133 -133
  109. gaia/apps/summarize/pdf_formatter.py +284 -284
  110. gaia/audio/__init__.py +2 -2
  111. gaia/audio/audio_client.py +439 -439
  112. gaia/audio/audio_recorder.py +269 -269
  113. gaia/audio/kokoro_tts.py +599 -599
  114. gaia/audio/whisper_asr.py +432 -432
  115. gaia/chat/__init__.py +16 -16
  116. gaia/chat/app.py +430 -430
  117. gaia/chat/prompts.py +522 -522
  118. gaia/chat/sdk.py +1228 -1225
  119. gaia/cli.py +5481 -5632
  120. gaia/database/__init__.py +10 -10
  121. gaia/database/agent.py +176 -176
  122. gaia/database/mixin.py +290 -290
  123. gaia/database/testing.py +64 -64
  124. gaia/eval/batch_experiment.py +2332 -2332
  125. gaia/eval/claude.py +542 -542
  126. gaia/eval/config.py +37 -37
  127. gaia/eval/email_generator.py +512 -512
  128. gaia/eval/eval.py +3179 -3179
  129. gaia/eval/groundtruth.py +1130 -1130
  130. gaia/eval/transcript_generator.py +582 -582
  131. gaia/eval/webapp/README.md +167 -167
  132. gaia/eval/webapp/package-lock.json +875 -875
  133. gaia/eval/webapp/package.json +20 -20
  134. gaia/eval/webapp/public/app.js +3402 -3402
  135. gaia/eval/webapp/public/index.html +87 -87
  136. gaia/eval/webapp/public/styles.css +3661 -3661
  137. gaia/eval/webapp/server.js +415 -415
  138. gaia/eval/webapp/test-setup.js +72 -72
  139. gaia/llm/__init__.py +9 -2
  140. gaia/llm/base_client.py +60 -0
  141. gaia/llm/exceptions.py +12 -0
  142. gaia/llm/factory.py +70 -0
  143. gaia/llm/lemonade_client.py +3236 -3221
  144. gaia/llm/lemonade_manager.py +294 -294
  145. gaia/llm/providers/__init__.py +9 -0
  146. gaia/llm/providers/claude.py +108 -0
  147. gaia/llm/providers/lemonade.py +120 -0
  148. gaia/llm/providers/openai_provider.py +79 -0
  149. gaia/llm/vlm_client.py +382 -382
  150. gaia/logger.py +189 -189
  151. gaia/mcp/agent_mcp_server.py +245 -245
  152. gaia/mcp/blender_mcp_client.py +138 -138
  153. gaia/mcp/blender_mcp_server.py +648 -648
  154. gaia/mcp/context7_cache.py +332 -332
  155. gaia/mcp/external_services.py +518 -518
  156. gaia/mcp/mcp_bridge.py +811 -550
  157. gaia/mcp/servers/__init__.py +6 -6
  158. gaia/mcp/servers/docker_mcp.py +83 -83
  159. gaia/perf_analysis.py +361 -0
  160. gaia/rag/__init__.py +10 -10
  161. gaia/rag/app.py +293 -293
  162. gaia/rag/demo.py +304 -304
  163. gaia/rag/pdf_utils.py +235 -235
  164. gaia/rag/sdk.py +2194 -2194
  165. gaia/security.py +163 -163
  166. gaia/talk/app.py +289 -289
  167. gaia/talk/sdk.py +538 -538
  168. gaia/testing/__init__.py +87 -87
  169. gaia/testing/assertions.py +330 -330
  170. gaia/testing/fixtures.py +333 -333
  171. gaia/testing/mocks.py +493 -493
  172. gaia/util.py +46 -46
  173. gaia/utils/__init__.py +33 -33
  174. gaia/utils/file_watcher.py +675 -675
  175. gaia/utils/parsing.py +223 -223
  176. gaia/version.py +100 -100
  177. amd_gaia-0.15.0.dist-info/RECORD +0 -168
  178. gaia/agents/code/app.py +0 -266
  179. gaia/llm/llm_client.py +0 -723
  180. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
  181. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/top_level.txt +0 -0
@@ -1,1016 +1,1016 @@
1
- # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
- # SPDX-License-Identifier: MIT
3
- """Project management tools mixin for Code Agent."""
4
-
5
- import ast
6
- import os
7
- from pathlib import Path
8
- from typing import Any, Dict, List
9
-
10
-
11
- class ProjectManagementMixin:
12
- """Mixin providing project-level management and creation tools.
13
-
14
- This mixin provides tools for:
15
- - Creating complete project structures from requirements
16
- - Listing and validating project files
17
- - Comprehensive project validation (structure, requirements, code quality)
18
- - Multi-language support (Python, JavaScript/TypeScript, CSS, HTML)
19
-
20
- Tools provided:
21
- - list_files: List files and directories in a path
22
- - validate_project: Comprehensive project validation with auto-fix capability
23
- - create_project: Generate complete project from natural language description
24
-
25
- Helper methods:
26
- - _validate_project_structure: Validate project structure for consistency
27
- """
28
-
29
- def register_project_management_tools(self) -> None:
30
- """Register project management tools."""
31
- from gaia.agents.base.tools import tool
32
-
33
- @tool
34
- def list_files(path: str = ".") -> Dict[str, Any]:
35
- """List files and directories in the specified path.
36
-
37
- Args:
38
- path: Directory path to list (default: current directory)
39
-
40
- Returns:
41
- Dictionary with list of files and directories
42
- """
43
- try:
44
- items = os.listdir(path)
45
- files = [
46
- item for item in items if os.path.isfile(os.path.join(path, item))
47
- ]
48
- dirs = [
49
- item for item in items if os.path.isdir(os.path.join(path, item))
50
- ]
51
-
52
- return {
53
- "status": "success",
54
- "path": path,
55
- "files": sorted(files),
56
- "directories": sorted(dirs),
57
- "total": len(items),
58
- }
59
- except FileNotFoundError:
60
- return {"status": "error", "error": f"Directory not found: {path}"}
61
- except PermissionError:
62
- return {"status": "error", "error": f"Permission denied: {path}"}
63
- except Exception as e:
64
- return {"status": "error", "error": str(e)}
65
-
66
- @tool
67
- def validate_project(project_path: str, fix: bool = False) -> Dict[str, Any]:
68
- """Comprehensive project validation for all file types and quality checks.
69
-
70
- This is the unified validation entry point that checks:
71
- - Python files: pylint, anti-patterns, Black formatting
72
- - JavaScript/TypeScript: ESLint (if available)
73
- - requirements.txt: hallucination detection
74
- - Project structure: entry points, essential files
75
- - CSS/HTML: basic validation
76
-
77
- Args:
78
- project_path: Path to the project directory
79
- fix: Whether to auto-fix issues where possible
80
-
81
- Returns:
82
- Dictionary with comprehensive validation results
83
- """
84
- try:
85
- path = Path(project_path)
86
- if not path.exists():
87
- return {
88
- "status": "error",
89
- "error": f"Project not found: {project_path}",
90
- }
91
-
92
- results = {
93
- "status": "success",
94
- "project": str(path),
95
- "validations": {},
96
- "total_errors": 0,
97
- "total_warnings": 0,
98
- "is_valid": True,
99
- }
100
-
101
- # Get all project files
102
- all_files = list(path.rglob("*"))
103
- py_files = [f for f in all_files if f.suffix == ".py"]
104
- js_files = [
105
- f for f in all_files if f.suffix in [".js", ".jsx", ".ts", ".tsx"]
106
- ]
107
- css_files = [f for f in all_files if f.suffix in [".css", ".scss"]]
108
- html_files = [f for f in all_files if f.suffix in [".html", ".htm"]]
109
-
110
- # 1. Check project structure
111
- structure_result = self._validate_project_structure(path, all_files)
112
- results["validations"]["structure"] = structure_result
113
- results["total_errors"] += len(structure_result.get("errors", []))
114
- results["total_warnings"] += len(structure_result.get("warnings", []))
115
-
116
- # 2. Validate requirements.txt
117
- req_file = path / "requirements.txt"
118
- if req_file.exists():
119
- req_result = self._validate_requirements(req_file, fix)
120
- results["validations"]["requirements"] = req_result
121
- results["total_errors"] += len(req_result.get("errors", []))
122
- results["total_warnings"] += len(req_result.get("warnings", []))
123
-
124
- # 3. Validate Python files
125
- if py_files:
126
- py_result = self._validate_python_files(py_files, fix)
127
- results["validations"]["python"] = py_result
128
- results["total_errors"] += py_result.get("total_errors", 0)
129
- results["total_warnings"] += py_result.get("total_warnings", 0)
130
-
131
- # 4. Validate JavaScript/TypeScript files
132
- if js_files:
133
- js_result = self._validate_javascript_files(js_files, fix)
134
- results["validations"]["javascript"] = js_result
135
- results["total_errors"] += js_result.get("total_errors", 0)
136
- results["total_warnings"] += js_result.get("total_warnings", 0)
137
-
138
- # 5. Basic validation for CSS files
139
- if css_files:
140
- css_result = self._validate_css_files(css_files)
141
- results["validations"]["css"] = css_result
142
- results["total_warnings"] += css_result.get("warnings", 0)
143
-
144
- # 6. Basic validation for HTML files
145
- if html_files:
146
- html_result = self._validate_html_files(html_files)
147
- results["validations"]["html"] = html_result
148
- results["total_warnings"] += html_result.get("warnings", 0)
149
-
150
- # Overall status
151
- results["is_valid"] = results["total_errors"] == 0
152
- if results["is_valid"]:
153
- if results["total_warnings"] > 0:
154
- results["message"] = (
155
- f"Validation passed with {results['total_warnings']} warnings"
156
- )
157
- else:
158
- results["message"] = "All validations passed!"
159
- else:
160
- results["message"] = (
161
- f"Validation failed: {results['total_errors']} errors, "
162
- f"{results['total_warnings']} warnings"
163
- )
164
-
165
- return results
166
-
167
- except Exception as e:
168
- return {"status": "error", "error": str(e)}
169
-
170
- @tool
171
- def create_project(query: str) -> Dict[str, Any]:
172
- """Create a complete Python project from requirements.
173
-
174
- Workflow:
175
- 1. Generate detailed plan with architecture and class/function outlines
176
- 2. Implement files one-by-one with validation
177
- 3. Generate comprehensive tests
178
- 4. Run tests and fix issues
179
- 5. Final validation and fixes
180
- 6. Summary report
181
-
182
- Args:
183
- query: The project requirements/description
184
-
185
- Returns:
186
- Dictionary with project creation results
187
- """
188
- try:
189
- self.console.print_header("🚀 Starting Project Generation")
190
-
191
- # Phase 1: Generate detailed architectural plan
192
- self.console.print_info("📋 Phase 1: Creating architectural plan...")
193
-
194
- plan_prompt = f"""Create a detailed architectural plan for: {query}
195
-
196
- Generate a comprehensive JSON response with:
197
- {{
198
- "project_name": "short_snake_case_name",
199
- "architecture": {{
200
- "overview": "Detailed description of what this application does",
201
- "patterns": ["List", "of", "design", "patterns"],
202
- "technologies": ["List", "of", "frameworks", "libraries", "databases"]
203
- }},
204
- "modules": [
205
- {{
206
- "name": "filename.py",
207
- "purpose": "What this module does",
208
- "classes": [
209
- {{"name": "ClassName", "purpose": "What this class handles", "methods": ["method1", "method2"]}}
210
- ],
211
- "functions": [
212
- {{"name": "function_name", "signature": "function_name(args) -> ReturnType", "purpose": "What it does"}}
213
- ]
214
- }}
215
- ],
216
- "tests": [
217
- {{"name": "test_filename.py", "coverage": "What this test file covers"}}
218
- ]
219
- }}
220
-
221
- IMPORTANT: Include ALL modules needed for a complete, working application.
222
- For the given requirements, think about:
223
- - Entry points and main application flow
224
- - Data models and database structure
225
- - Business logic and services
226
- - API endpoints or user interface
227
- - Authentication and security
228
- - Configuration and utilities
229
- - Comprehensive test coverage
230
-
231
- Return ONLY valid JSON."""
232
-
233
- plan_response = self.chat.send(plan_prompt, max_tokens=3000).text
234
-
235
- import json
236
-
237
- try:
238
- # Clean the response if it has markdown code blocks
239
- if "```json" in plan_response:
240
- plan_response = plan_response.split("```json")[1].split("```")[
241
- 0
242
- ]
243
- elif "```" in plan_response:
244
- plan_response = plan_response.split("```")[1].split("```")[0]
245
-
246
- plan_data = json.loads(plan_response)
247
- project_name = plan_data.get("project_name", "my_project")
248
-
249
- # Basic sanitization - lowercase and replace spaces
250
- project_name = (
251
- project_name.lower().strip().replace(" ", "_").replace("-", "_")
252
- )
253
-
254
- # Check if name is valid (not too long, no special chars, doesn't already exist)
255
- max_retries = 3
256
- for retry in range(max_retries):
257
- issues = []
258
-
259
- # Check if folder already exists
260
- if os.path.exists(project_name):
261
- issues.append(f"folder '{project_name}' already exists")
262
-
263
- # Check if name is too long
264
- if len(project_name) > 30:
265
- issues.append(
266
- f"name too long ({len(project_name)} chars, max 30)"
267
- )
268
-
269
- # Check if name has invalid characters
270
- if not project_name.replace("_", "").replace(".", "").isalnum():
271
- issues.append(
272
- "name contains invalid characters (only a-z, 0-9, _ allowed)"
273
- )
274
-
275
- # Check if name is empty
276
- if not project_name or project_name == "_":
277
- issues.append("name is empty or invalid")
278
-
279
- # If valid, break
280
- if not issues:
281
- break
282
-
283
- # Ask LLM to fix the name
284
- if retry < max_retries - 1:
285
- fix_prompt = f"""The project name "{project_name}" has issues: {', '.join(issues)}
286
-
287
- Please provide a new, valid project name that:
288
- - Is short and descriptive (max 30 characters)
289
- - Uses snake_case (lowercase with underscores)
290
- - Doesn't already exist
291
- - Only contains letters, numbers, and underscores
292
-
293
- Original project description: {query}
294
-
295
- Respond with ONLY the new project name, nothing else."""
296
-
297
- new_name = self.chat.send(
298
- fix_prompt, max_tokens=50
299
- ).text.strip()
300
- # Clean the response
301
- new_name = (
302
- new_name.lower()
303
- .strip()
304
- .replace(" ", "_")
305
- .replace("-", "_")
306
- )
307
- # Remove quotes if present
308
- new_name = new_name.strip('"').strip("'")
309
-
310
- self.console.print_warning(
311
- f"⚠️ Project name '{project_name}' invalid: {', '.join(issues)}"
312
- )
313
- self.console.print_info(f" Retrying with: '{new_name}'")
314
- project_name = new_name
315
- else:
316
- # Last resort - generate unique name
317
- import random
318
-
319
- project_name = f"project_{random.randint(1000, 9999)}"
320
- self.console.print_warning(
321
- f"⚠️ Using fallback name: '{project_name}'"
322
- )
323
- break
324
-
325
- modules = plan_data.get("modules", [])
326
- test_modules = plan_data.get("tests", [])
327
- architecture = plan_data.get("architecture", {})
328
-
329
- # Ensure we have at least basic files
330
- if not any(m["name"] == "requirements.txt" for m in modules):
331
- modules.append(
332
- {
333
- "name": "requirements.txt",
334
- "purpose": "Python dependencies",
335
- }
336
- )
337
- if not any(m["name"] == "README.md" for m in modules):
338
- modules.append(
339
- {"name": "README.md", "purpose": "Project documentation"}
340
- )
341
-
342
- except (json.JSONDecodeError, TypeError) as e:
343
- self.console.print_warning(
344
- f"⚠️ Could not parse JSON plan, using fallback: {str(e)[:100]}"
345
- )
346
- # Fallback plan
347
- project_name = "my_project"
348
- modules = [
349
- {"name": "main.py", "purpose": "Entry point"},
350
- {"name": "core.py", "purpose": "Core logic"},
351
- {"name": "requirements.txt", "purpose": "Dependencies"},
352
- {"name": "README.md", "purpose": "Documentation"},
353
- ]
354
- test_modules = [{"name": "test_main.py", "coverage": "main tests"}]
355
- architecture = {"overview": query}
356
-
357
- # Create project directory
358
- os.makedirs(project_name, exist_ok=True)
359
- created_files = []
360
- implementation_issues = []
361
- test_results = {}
362
-
363
- # Write detailed PLAN.md with architecture and outlines
364
- plan_content = f"""# {project_name} - Architectural Plan
365
-
366
- ## Project Overview
367
- {architecture.get('overview', query)}
368
-
369
- ## Architecture
370
- - **Patterns**: {', '.join(architecture.get('patterns', ['Modular']))}
371
- - **Technologies**: {', '.join(architecture.get('technologies', ['Python']))}
372
-
373
- ## Implementation Tasks
374
-
375
- ### Phase 1: Project Setup
376
- - [ ] Create project structure
377
- - [ ] Generate PLAN.md
378
- - [ ] Set up requirements.txt
379
-
380
- ### Phase 2: Core Modules
381
- """
382
- for module in modules:
383
- plan_content += f"- [ ] Implement `{module['name']}` - {module.get('purpose', 'Core functionality')}\n"
384
-
385
- plan_content += "\n### Phase 3: Test Suite\n"
386
- for test in test_modules:
387
- plan_content += f"- [ ] Generate `{test['name']}` - {test.get('coverage', 'Unit tests')}\n"
388
-
389
- plan_content += """
390
- ### Phase 4: Quality Assurance
391
- - [ ] Run all tests
392
- - [ ] Fix any test failures
393
- - [ ] Apply Black formatting
394
- - [ ] Fix linting issues
395
-
396
- ### Phase 5: Documentation
397
- - [ ] Verify README.md completeness
398
- - [ ] Add usage examples
399
- - [ ] Document API if applicable
400
-
401
- ## Module Specifications
402
- """
403
- for module in modules:
404
- plan_content += f"\n### {module['name']}\n"
405
- plan_content += (
406
- f"**Purpose**: {module.get('purpose', 'Implementation')}\n\n"
407
- )
408
-
409
- if module.get("classes"):
410
- plan_content += "**Classes**:\n"
411
- for cls in module["classes"]:
412
- plan_content += (
413
- f"- `{cls['name']}`: {cls.get('purpose', '')}\n"
414
- )
415
- if cls.get("methods"):
416
- plan_content += (
417
- f" - Methods: {', '.join(cls['methods'])}\n"
418
- )
419
-
420
- if module.get("functions"):
421
- plan_content += "\n**Functions**:\n"
422
- for func in module["functions"]:
423
- plan_content += f"- `{func.get('signature', func['name'])}`: {func.get('purpose', '')}\n"
424
-
425
- plan_content += "\n## Test Coverage\n"
426
- for test in test_modules:
427
- plan_content += (
428
- f"- **{test['name']}**: {test.get('coverage', 'Tests')}\n"
429
- )
430
-
431
- plan_content += "\n## Implementation Order\n"
432
- plan_content += "1. Core modules and data structures\n"
433
- plan_content += "2. Business logic implementation\n"
434
- plan_content += "3. Integration and API layers\n"
435
- plan_content += "4. Comprehensive test suite\n"
436
- plan_content += "5. Documentation and examples\n"
437
-
438
- # Start streaming preview for PLAN.md (markdown file)
439
- self.console.start_file_preview(
440
- "PLAN.md", max_lines=20, title_prefix="📋"
441
- )
442
-
443
- # Stream the content in chunks for visual effect
444
- plan_chunks = []
445
- chunk_size = 500 # Characters per chunk
446
- for i in range(0, len(plan_content), chunk_size):
447
- chunk = plan_content[i : i + chunk_size]
448
- plan_chunks.append(chunk)
449
- self.console.update_file_preview(chunk)
450
- # Small delay for visual streaming effect
451
- import time
452
-
453
- time.sleep(0.05)
454
-
455
- # End the preview
456
- self.console.stop_file_preview()
457
-
458
- # Write PLAN.md
459
- plan_path = os.path.join(project_name, "PLAN.md")
460
- with open(plan_path, "w", encoding="utf-8") as f:
461
- f.write(plan_content)
462
- created_files.append(plan_path)
463
-
464
- plan_lines = plan_content.count("\n") + 1
465
- self.console.print_success(
466
- f"✅ Created detailed PLAN.md with architecture ({plan_lines} lines)"
467
- )
468
-
469
- # Project setup phase complete - agent can now use update_plan_progress tool
470
-
471
- # Phase 2: Implement files one-by-one with validation
472
- self.console.print_info(
473
- "\n📝 Phase 2: Implementing modules one-by-one..."
474
- )
475
-
476
- # Sort modules to implement core/config files first
477
- priority_order = ["config", "models", "database", "utils", "core"]
478
- modules_sorted = sorted(
479
- modules,
480
- key=lambda m: (
481
- 0
482
- if "requirements.txt" in m["name"]
483
- else (
484
- 1
485
- if any(p in m["name"].lower() for p in priority_order)
486
- else 2 if "main.py" in m["name"] else 3
487
- )
488
- ),
489
- )
490
-
491
- # Initial load of PLAN.md
492
- plan_path = os.path.join(project_name, "PLAN.md")
493
-
494
- for i, module in enumerate(modules_sorted, 1):
495
- filename = module["name"]
496
- progress_pct = int((i / len(modules)) * 100)
497
- self.console.print_info(
498
- f" [{i}/{len(modules)}] ({progress_pct}%) Generating {filename}..."
499
- )
500
-
501
- file_path = os.path.join(project_name, filename)
502
-
503
- # Create subdirectories if needed
504
- dir_path = os.path.dirname(file_path)
505
- if dir_path:
506
- os.makedirs(dir_path, exist_ok=True)
507
-
508
- # Re-read PLAN.md each time to get latest updates
509
- if os.path.exists(plan_path):
510
- with open(plan_path, "r", encoding="utf-8") as f:
511
- plan_context = f.read()
512
- else:
513
- plan_context = plan_content
514
-
515
- # Generate with architecture context including latest PLAN.md
516
- context = (
517
- f"{query}\n\nProject Plan (Current State):\n{plan_context}\n\n"
518
- f"Current Module:\n{json.dumps(module, indent=2)}"
519
- )
520
- code = self._generate_code_for_file(
521
- filename=filename,
522
- purpose=module.get("purpose", ""),
523
- context=context,
524
- )
525
-
526
- # Write initial version
527
- with open(file_path, "w", encoding="utf-8") as f:
528
- f.write(code)
529
-
530
- # Validate and fix iteratively (max 3 attempts)
531
- if filename.endswith(".py"):
532
- for attempt in range(3):
533
- try:
534
- # Syntax check
535
-
536
- ast.parse(code)
537
-
538
- # Anti-pattern check
539
- antipattern_result = self._check_antipatterns(
540
- Path(file_path), code
541
- )
542
- if antipattern_result["errors"]:
543
- self.console.print_warning(
544
- f" ⚠️ Anti-patterns detected: {len(antipattern_result['errors'])} issues"
545
- )
546
- implementation_issues.append(
547
- {
548
- "file": filename,
549
- "type": "antipattern",
550
- "issues": antipattern_result["errors"][:2],
551
- }
552
- )
553
-
554
- # If syntax is valid, we're done
555
- self.console.print_success(
556
- f" ✅ {filename} validated"
557
- )
558
- break
559
-
560
- except SyntaxError as e:
561
- if attempt < 2:
562
- self.console.print_warning(
563
- f" 🔧 Fixing syntax error (attempt {attempt+1}/3)"
564
- )
565
- code = self._fix_code_with_llm(
566
- code, file_path, str(e)
567
- )
568
- with open(file_path, "w", encoding="utf-8") as f:
569
- f.write(code)
570
- else:
571
- self.console.print_error(
572
- f" ❌ Could not fix syntax in {filename}"
573
- )
574
- implementation_issues.append(
575
- {
576
- "file": filename,
577
- "type": "syntax",
578
- "error": str(e),
579
- }
580
- )
581
-
582
- created_files.append(file_path)
583
-
584
- # Phase 3: Generate comprehensive tests
585
- self.console.print_info("\n🧪 Phase 3: Generating test suite...")
586
-
587
- for i, test in enumerate(test_modules, 1):
588
- test_filename = test["name"]
589
- progress_pct = int((i / len(test_modules)) * 100)
590
- self.console.print_info(
591
- f" [{i}/{len(test_modules)}] ({progress_pct}%) Generating {test_filename}..."
592
- )
593
-
594
- test_path = os.path.join(project_name, test_filename)
595
-
596
- # Re-read PLAN.md to get latest updates
597
- if os.path.exists(plan_path):
598
- with open(plan_path, "r", encoding="utf-8") as f:
599
- plan_context = f.read()
600
- else:
601
- plan_context = plan_content
602
-
603
- # Generate test with context about what to test and latest PLAN.md
604
- test_context = f"{query}\n\nProject Plan (Current State):\n{plan_context}\n\nTest Coverage: {test.get('coverage', '')}\n\nModules to test:\n"
605
- for module in modules:
606
- if not module["name"].startswith("test_"):
607
- test_context += (
608
- f"- {module['name']}: {module.get('purpose', '')}\n"
609
- )
610
-
611
- # Add timeout handling for test generation
612
- try:
613
- import threading
614
-
615
- test_code = None
616
- generation_error = None
617
-
618
- def generate_with_timeout(
619
- test_filename_param, test_param, test_context_param
620
- ):
621
- nonlocal test_code, generation_error
622
- try:
623
- test_code = self._generate_code_for_file(
624
- filename=test_filename_param,
625
- purpose=f"Unit tests for {test_param.get('coverage', 'functionality')}",
626
- context=test_context_param,
627
- )
628
- except Exception as e:
629
- generation_error = e
630
-
631
- # Run generation in a thread with timeout
632
- gen_thread = threading.Thread(
633
- target=generate_with_timeout,
634
- args=(test_filename, test, test_context),
635
- )
636
- gen_thread.daemon = True
637
- gen_thread.start()
638
- gen_thread.join(
639
- timeout=180
640
- ) # 180 second (3 min) timeout for test generation
641
-
642
- if gen_thread.is_alive():
643
- self.console.print_warning(
644
- f" ⚠️ Test generation timeout for {test_filename}, using placeholder..."
645
- )
646
- # Generate a simple placeholder test
647
- test_code = f'''"""Unit tests for {test.get('coverage', 'functionality')}."""
648
- import unittest
649
-
650
- class TestPlaceholder(unittest.TestCase):
651
- """Placeholder tests - generation timed out."""
652
-
653
- def test_placeholder(self):
654
- """Placeholder test - needs implementation."""
655
- self.skipTest("Test generation timed out - needs manual implementation")
656
-
657
- if __name__ == "__main__":
658
- unittest.main()
659
- '''
660
- elif generation_error is not None:
661
- if isinstance(generation_error, Exception):
662
- raise generation_error
663
- else:
664
- raise Exception(
665
- f"Test generation error: {generation_error}"
666
- )
667
- elif not test_code:
668
- raise Exception("No test code generated")
669
-
670
- except Exception as e:
671
- self.console.print_warning(
672
- f" ⚠️ Failed to generate {test_filename}: {str(e)[:100]}"
673
- )
674
- # Generate a simple placeholder test
675
- test_code = f'''"""Unit tests for {test.get('coverage', 'functionality')}."""
676
- import unittest
677
-
678
- class TestPlaceholder(unittest.TestCase):
679
- """Placeholder tests - generation failed."""
680
-
681
- def test_placeholder(self):
682
- """Placeholder test - needs implementation."""
683
- self.skipTest("Test generation failed - needs manual implementation")
684
-
685
- if __name__ == "__main__":
686
- unittest.main()
687
- '''
688
-
689
- with open(test_path, "w", encoding="utf-8") as f:
690
- f.write(test_code)
691
-
692
- # Validate test file syntax
693
- try:
694
-
695
- ast.parse(test_code)
696
- self.console.print_success(f" ✅ {test_filename} validated")
697
- except SyntaxError as e:
698
- self.console.print_warning(
699
- f" ⚠️ Syntax issues in {test_filename}, attempting fix..."
700
- )
701
- test_code = self._fix_code_with_llm(
702
- test_code, test_path, str(e)
703
- )
704
- with open(test_path, "w", encoding="utf-8") as f:
705
- f.write(test_code)
706
-
707
- created_files.append(test_path)
708
-
709
- # Phase 3.5: Apply Black formatting to all Python files
710
- self.console.print_info(
711
- "\n🎨 Phase 3.5: Applying Black formatting to all Python files..."
712
- )
713
-
714
- # Format all Python files in the project
715
- python_files = []
716
- for f in created_files:
717
- file_path = Path(f) if isinstance(f, str) else f
718
- if file_path.suffix == ".py":
719
- python_files.append(file_path)
720
-
721
- formatted_count = 0
722
-
723
- for py_file in python_files:
724
- if py_file.exists():
725
- format_result = self._execute_tool(
726
- "format_with_black", {"file_path": str(py_file)}
727
- )
728
- if format_result.get("formatted"):
729
- formatted_count += 1
730
-
731
- if formatted_count > 0:
732
- self.console.print_success(
733
- f"✅ Formatted {formatted_count} Python file(s) with Black"
734
- )
735
- else:
736
- self.console.print_info(
737
- "✓ All Python files already properly formatted"
738
- )
739
-
740
- # Phase 4: Run tests and fix issues
741
- self.console.print_info(
742
- "\n🏃 Phase 4: Running tests and fixing issues..."
743
- )
744
-
745
- test_run_result = self._execute_tool(
746
- "run_tests", {"project_path": project_name, "timeout": 30}
747
- )
748
- if test_run_result.get("status") == "success":
749
- if test_run_result.get("tests_passed"):
750
- self.console.print_success("✅ All tests passed!")
751
- test_results["status"] = "passed"
752
- test_results["details"] = "All tests executed successfully"
753
- else:
754
- # Show test failure details
755
- failure_summary = test_run_result.get("failure_summary", "")
756
- stdout = test_run_result.get("stdout", "")
757
-
758
- self.console.print_warning(
759
- f"⚠️ Some tests failed: {failure_summary}"
760
- if failure_summary
761
- else "⚠️ Some tests failed, attempting fixes..."
762
- )
763
-
764
- # Extract and show failed test names from pytest output
765
- if stdout:
766
- import re
767
-
768
- # Get command that was run
769
- test_command = test_run_result.get("command", "pytest")
770
-
771
- # Look for FAILED lines in pytest output
772
- failed_tests = re.findall(r"FAILED (.*?) -", stdout)
773
- if failed_tests:
774
- self.console.print_info(
775
- f"\n Failed tests ({len(failed_tests)}):"
776
- )
777
- for test in failed_tests[:5]: # Show first 5
778
- self.console.print_info(f" • {test}")
779
- if len(failed_tests) > 5:
780
- self.console.print_info(
781
- f" ... and {len(failed_tests) - 5} more"
782
- )
783
-
784
- # Show terminal output preview - just raw output in a panel
785
- # Take first 20 lines of pytest output
786
- lines = stdout.split("\n")[:20]
787
- if lines:
788
- if (
789
- hasattr(self.console, "console")
790
- and self.console.console
791
- ):
792
- from rich.panel import Panel
793
-
794
- # Show command and raw output
795
- preview_text = f"$ {test_command}\n\n" + "\n".join(
796
- lines
797
- )
798
- self.console.console.print(
799
- Panel(
800
- preview_text,
801
- title="Test Output Preview",
802
- border_style="yellow",
803
- expand=False,
804
- )
805
- )
806
- else:
807
- print("\n Test Output Preview:")
808
- print(" " + "─" * 70)
809
- print(f" $ {test_command}\n")
810
- for line in lines:
811
- print(f" {line}")
812
- print(" " + "─" * 70)
813
-
814
- test_results["status"] = "partial"
815
- test_results["stderr"] = test_run_result.get("stderr", "")
816
- test_results["stdout"] = stdout
817
- test_results["failure_summary"] = failure_summary
818
-
819
- # Try to fix test failures
820
- for attempt in range(2):
821
- self.console.print_info(
822
- f" 🔧 Fix attempt {attempt+1}/2..."
823
- )
824
-
825
- # Run auto_fix_syntax_errors on the project
826
- fix_result = self._execute_tool(
827
- "auto_fix_syntax_errors", {"project_path": project_name}
828
- )
829
- if fix_result.get("files_fixed"):
830
- fixed_files = fix_result["files_fixed"]
831
- self.console.print_info(
832
- f" Fixed {len(fixed_files)} files:"
833
- )
834
- for file in fixed_files[:3]: # Show first 3
835
- self.console.print_info(f" • {file}")
836
- if len(fixed_files) > 3:
837
- self.console.print_info(
838
- f" ... and {len(fixed_files) - 3} more"
839
- )
840
-
841
- # Re-run tests
842
- self.console.print_info(" Re-running tests...")
843
- test_run_result = self._execute_tool(
844
- "run_tests",
845
- {"project_path": project_name, "timeout": 30},
846
- )
847
- if test_run_result.get("tests_passed"):
848
- self.console.print_success(
849
- " ✅ Tests now passing!"
850
- )
851
- test_results["status"] = "passed"
852
- break
853
- else:
854
- # Show what's still failing
855
- new_failure_summary = test_run_result.get(
856
- "failure_summary", ""
857
- )
858
- if new_failure_summary:
859
- self.console.print_warning(
860
- f" Still failing: {new_failure_summary}"
861
- )
862
- else:
863
- self.console.print_info(
864
- " No syntax errors found to fix"
865
- )
866
- else:
867
- self.console.print_warning(
868
- f"⚠️ Could not run tests: {test_run_result.get('error', 'Unknown error')}"
869
- )
870
- test_results["status"] = "error"
871
- test_results["error"] = test_run_result.get("error", "")
872
-
873
- # Phase 5: Final comprehensive validation
874
- self.console.print_info("\n🔍 Phase 5: Final project validation...")
875
-
876
- final_validation = self._execute_tool(
877
- "validate_project", {"project_path": project_name, "fix": True}
878
- )
879
-
880
- # Try to fix any remaining issues
881
- if not final_validation.get("is_valid"):
882
- self.console.print_info(" 🔧 Attempting final fixes...")
883
-
884
- for attempt in range(2):
885
- if final_validation.get("total_errors", 0) == 0:
886
- break
887
-
888
- # Run auto-fix
889
- auto_fix_result = self._execute_tool(
890
- "auto_fix_syntax_errors", {"project_path": project_name}
891
- )
892
- if auto_fix_result.get("files_fixed"):
893
- self.console.print_info(
894
- f" Fixed {len(auto_fix_result['files_fixed'])} files"
895
- )
896
-
897
- # Re-validate
898
- final_validation = self._execute_tool(
899
- "validate_project",
900
- {"project_path": project_name, "fix": True},
901
- )
902
- if final_validation.get("is_valid"):
903
- self.console.print_success(
904
- "✅ All validation checks passed!"
905
- )
906
- break
907
-
908
- # Phase 6: Generate summary
909
- self.console.print_header("\n📊 Project Generation Complete!")
910
-
911
- # Build summary message
912
- summary = f"""
913
- ## Project: {project_name}
914
-
915
- ### 📋 Architecture
916
- - **Overview**: {architecture.get('overview', query)[:100]}...
917
- - **Technologies**: {', '.join(architecture.get('technologies', ['Python']))}
918
- - **Patterns**: {', '.join(architecture.get('patterns', ['Modular']))}
919
-
920
- ### 📁 Generated Files ({len(created_files)} total)
921
- - **Core Modules**: {len([f for f in created_files if f.endswith('.py') and 'test' not in f])}
922
- - **Test Files**: {len([f for f in created_files if 'test' in f])}
923
- - **Documentation**: {len([f for f in created_files if f.endswith('.md')])}
924
- - **Configuration**: {len([f for f in created_files if f.endswith(('.txt', '.yml', '.yaml', '.json'))])}
925
-
926
- ### ✅ Quality Metrics
927
- - **Syntax Validation**: {'✅ Passed' if not implementation_issues else f'⚠️ {len(implementation_issues)} issues'}
928
- - **Test Results**: {test_results.get('status', 'Not run').title()}
929
- - **Code Quality**: {final_validation.get('total_errors', 0)} errors, {final_validation.get('total_warnings', 0)} warnings
930
- - **Anti-patterns**: {'None detected' if not any(i['type'] == 'antipattern' for i in implementation_issues) else 'Some detected'}
931
-
932
- ### 🎯 Ready to Use
933
- The project is structured and ready for development. Key features:
934
- {chr(10).join(f"- {module['name']}: {module.get('purpose', '')}" for module in modules[:5])}
935
-
936
- ### 🚀 Next Steps
937
- 1. Review PLAN.md for architecture details
938
- 2. Install dependencies: `pip install -r requirements.txt`
939
- 3. Run the application: `python main.py`
940
- 4. Run tests: `pytest`
941
- """
942
-
943
- if hasattr(self.console, "console") and self.console.console:
944
- from rich.panel import Panel
945
-
946
- self.console.console.print(
947
- Panel(summary, title="Project Summary", expand=False)
948
- )
949
- else:
950
- print(summary)
951
-
952
- return {
953
- "status": "success",
954
- "project_name": project_name,
955
- "files_created": created_files,
956
- "validation": final_validation,
957
- "test_results": test_results,
958
- "implementation_issues": implementation_issues,
959
- "summary": summary,
960
- "message": f"✅ Successfully created {project_name} with {len(created_files)} files",
961
- }
962
-
963
- except Exception as e:
964
- return {"status": "error", "error": str(e)}
965
-
966
- def _validate_project_structure(
967
- self, _project_path: Path, files: List[Path]
968
- ) -> Dict[str, Any]:
969
- """Validate project structure for consistency issues.
970
-
971
- Args:
972
- _project_path: Path to the project directory (unused currently)
973
- files: List of Path objects for all files in the project
974
-
975
- Returns:
976
- Dictionary with validation results including errors and warnings
977
- """
978
- errors = []
979
- warnings = []
980
-
981
- filenames = [f.name for f in files if f.is_file()]
982
-
983
- # Check for multiple entry points (common issue)
984
- entry_points = ["main.py", "app.py", "run.py", "__main__.py", "wsgi.py"]
985
- found_entries = [ep for ep in entry_points if ep in filenames]
986
-
987
- if len(found_entries) > 1:
988
- errors.append(
989
- f"Multiple entry points found: {', '.join(found_entries)}. "
990
- "Choose ONE pattern: either monolithic or modular."
991
- )
992
-
993
- # Check for essential files
994
- if "README.md" not in filenames and "readme.md" not in filenames:
995
- errors.append("Missing README.md")
996
-
997
- if "requirements.txt" not in filenames and "pyproject.toml" not in filenames:
998
- warnings.append("Missing requirements.txt or pyproject.toml")
999
-
1000
- # Check for PLAN.md
1001
- if "PLAN.md" not in filenames and "plan.md" not in filenames:
1002
- warnings.append(
1003
- "No PLAN.md found (architectural plan should be created first)"
1004
- )
1005
-
1006
- # Check for duplicate models (common issue)
1007
- model_files = [
1008
- f for f in files if "model" in f.name.lower() and f.suffix == ".py"
1009
- ]
1010
- if len(model_files) > 2:
1011
- warnings.append(
1012
- f"Multiple model files found ({len(model_files)}). "
1013
- "Check for duplicate definitions."
1014
- )
1015
-
1016
- return {"is_valid": len(errors) == 0, "errors": errors, "warnings": warnings}
1
+ # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
+ # SPDX-License-Identifier: MIT
3
+ """Project management tools mixin for Code Agent."""
4
+
5
+ import ast
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Any, Dict, List
9
+
10
+
11
+ class ProjectManagementMixin:
12
+ """Mixin providing project-level management and creation tools.
13
+
14
+ This mixin provides tools for:
15
+ - Creating complete project structures from requirements
16
+ - Listing and validating project files
17
+ - Comprehensive project validation (structure, requirements, code quality)
18
+ - Multi-language support (Python, JavaScript/TypeScript, CSS, HTML)
19
+
20
+ Tools provided:
21
+ - list_files: List files and directories in a path
22
+ - validate_project: Comprehensive project validation with auto-fix capability
23
+ - create_project: Generate complete project from natural language description
24
+
25
+ Helper methods:
26
+ - _validate_project_structure: Validate project structure for consistency
27
+ """
28
+
29
+ def register_project_management_tools(self) -> None:
30
+ """Register project management tools."""
31
+ from gaia.agents.base.tools import tool
32
+
33
+ @tool
34
+ def list_files(path: str = ".") -> Dict[str, Any]:
35
+ """List files and directories in the specified path.
36
+
37
+ Args:
38
+ path: Directory path to list (default: current directory)
39
+
40
+ Returns:
41
+ Dictionary with list of files and directories
42
+ """
43
+ try:
44
+ items = os.listdir(path)
45
+ files = [
46
+ item for item in items if os.path.isfile(os.path.join(path, item))
47
+ ]
48
+ dirs = [
49
+ item for item in items if os.path.isdir(os.path.join(path, item))
50
+ ]
51
+
52
+ return {
53
+ "status": "success",
54
+ "path": path,
55
+ "files": sorted(files),
56
+ "directories": sorted(dirs),
57
+ "total": len(items),
58
+ }
59
+ except FileNotFoundError:
60
+ return {"status": "error", "error": f"Directory not found: {path}"}
61
+ except PermissionError:
62
+ return {"status": "error", "error": f"Permission denied: {path}"}
63
+ except Exception as e:
64
+ return {"status": "error", "error": str(e)}
65
+
66
+ @tool
67
+ def validate_project(project_path: str, fix: bool = False) -> Dict[str, Any]:
68
+ """Comprehensive project validation for all file types and quality checks.
69
+
70
+ This is the unified validation entry point that checks:
71
+ - Python files: pylint, anti-patterns, Black formatting
72
+ - JavaScript/TypeScript: ESLint (if available)
73
+ - requirements.txt: hallucination detection
74
+ - Project structure: entry points, essential files
75
+ - CSS/HTML: basic validation
76
+
77
+ Args:
78
+ project_path: Path to the project directory
79
+ fix: Whether to auto-fix issues where possible
80
+
81
+ Returns:
82
+ Dictionary with comprehensive validation results
83
+ """
84
+ try:
85
+ path = Path(project_path)
86
+ if not path.exists():
87
+ return {
88
+ "status": "error",
89
+ "error": f"Project not found: {project_path}",
90
+ }
91
+
92
+ results = {
93
+ "status": "success",
94
+ "project": str(path),
95
+ "validations": {},
96
+ "total_errors": 0,
97
+ "total_warnings": 0,
98
+ "is_valid": True,
99
+ }
100
+
101
+ # Get all project files
102
+ all_files = list(path.rglob("*"))
103
+ py_files = [f for f in all_files if f.suffix == ".py"]
104
+ js_files = [
105
+ f for f in all_files if f.suffix in [".js", ".jsx", ".ts", ".tsx"]
106
+ ]
107
+ css_files = [f for f in all_files if f.suffix in [".css", ".scss"]]
108
+ html_files = [f for f in all_files if f.suffix in [".html", ".htm"]]
109
+
110
+ # 1. Check project structure
111
+ structure_result = self._validate_project_structure(path, all_files)
112
+ results["validations"]["structure"] = structure_result
113
+ results["total_errors"] += len(structure_result.get("errors", []))
114
+ results["total_warnings"] += len(structure_result.get("warnings", []))
115
+
116
+ # 2. Validate requirements.txt
117
+ req_file = path / "requirements.txt"
118
+ if req_file.exists():
119
+ req_result = self._validate_requirements(req_file, fix)
120
+ results["validations"]["requirements"] = req_result
121
+ results["total_errors"] += len(req_result.get("errors", []))
122
+ results["total_warnings"] += len(req_result.get("warnings", []))
123
+
124
+ # 3. Validate Python files
125
+ if py_files:
126
+ py_result = self._validate_python_files(py_files, fix)
127
+ results["validations"]["python"] = py_result
128
+ results["total_errors"] += py_result.get("total_errors", 0)
129
+ results["total_warnings"] += py_result.get("total_warnings", 0)
130
+
131
+ # 4. Validate JavaScript/TypeScript files
132
+ if js_files:
133
+ js_result = self._validate_javascript_files(js_files, fix)
134
+ results["validations"]["javascript"] = js_result
135
+ results["total_errors"] += js_result.get("total_errors", 0)
136
+ results["total_warnings"] += js_result.get("total_warnings", 0)
137
+
138
+ # 5. Basic validation for CSS files
139
+ if css_files:
140
+ css_result = self._validate_css_files(css_files)
141
+ results["validations"]["css"] = css_result
142
+ results["total_warnings"] += css_result.get("warnings", 0)
143
+
144
+ # 6. Basic validation for HTML files
145
+ if html_files:
146
+ html_result = self._validate_html_files(html_files)
147
+ results["validations"]["html"] = html_result
148
+ results["total_warnings"] += html_result.get("warnings", 0)
149
+
150
+ # Overall status
151
+ results["is_valid"] = results["total_errors"] == 0
152
+ if results["is_valid"]:
153
+ if results["total_warnings"] > 0:
154
+ results["message"] = (
155
+ f"Validation passed with {results['total_warnings']} warnings"
156
+ )
157
+ else:
158
+ results["message"] = "All validations passed!"
159
+ else:
160
+ results["message"] = (
161
+ f"Validation failed: {results['total_errors']} errors, "
162
+ f"{results['total_warnings']} warnings"
163
+ )
164
+
165
+ return results
166
+
167
+ except Exception as e:
168
+ return {"status": "error", "error": str(e)}
169
+
170
+ @tool
171
+ def create_project(query: str) -> Dict[str, Any]:
172
+ """Create a complete Python project from requirements.
173
+
174
+ Workflow:
175
+ 1. Generate detailed plan with architecture and class/function outlines
176
+ 2. Implement files one-by-one with validation
177
+ 3. Generate comprehensive tests
178
+ 4. Run tests and fix issues
179
+ 5. Final validation and fixes
180
+ 6. Summary report
181
+
182
+ Args:
183
+ query: The project requirements/description
184
+
185
+ Returns:
186
+ Dictionary with project creation results
187
+ """
188
+ try:
189
+ self.console.print_header("🚀 Starting Project Generation")
190
+
191
+ # Phase 1: Generate detailed architectural plan
192
+ self.console.print_info("📋 Phase 1: Creating architectural plan...")
193
+
194
+ plan_prompt = f"""Create a detailed architectural plan for: {query}
195
+
196
+ Generate a comprehensive JSON response with:
197
+ {{
198
+ "project_name": "short_snake_case_name",
199
+ "architecture": {{
200
+ "overview": "Detailed description of what this application does",
201
+ "patterns": ["List", "of", "design", "patterns"],
202
+ "technologies": ["List", "of", "frameworks", "libraries", "databases"]
203
+ }},
204
+ "modules": [
205
+ {{
206
+ "name": "filename.py",
207
+ "purpose": "What this module does",
208
+ "classes": [
209
+ {{"name": "ClassName", "purpose": "What this class handles", "methods": ["method1", "method2"]}}
210
+ ],
211
+ "functions": [
212
+ {{"name": "function_name", "signature": "function_name(args) -> ReturnType", "purpose": "What it does"}}
213
+ ]
214
+ }}
215
+ ],
216
+ "tests": [
217
+ {{"name": "test_filename.py", "coverage": "What this test file covers"}}
218
+ ]
219
+ }}
220
+
221
+ IMPORTANT: Include ALL modules needed for a complete, working application.
222
+ For the given requirements, think about:
223
+ - Entry points and main application flow
224
+ - Data models and database structure
225
+ - Business logic and services
226
+ - API endpoints or user interface
227
+ - Authentication and security
228
+ - Configuration and utilities
229
+ - Comprehensive test coverage
230
+
231
+ Return ONLY valid JSON."""
232
+
233
+ plan_response = self.chat.send(plan_prompt, max_tokens=3000).text
234
+
235
+ import json
236
+
237
+ try:
238
+ # Clean the response if it has markdown code blocks
239
+ if "```json" in plan_response:
240
+ plan_response = plan_response.split("```json")[1].split("```")[
241
+ 0
242
+ ]
243
+ elif "```" in plan_response:
244
+ plan_response = plan_response.split("```")[1].split("```")[0]
245
+
246
+ plan_data = json.loads(plan_response)
247
+ project_name = plan_data.get("project_name", "my_project")
248
+
249
+ # Basic sanitization - lowercase and replace spaces
250
+ project_name = (
251
+ project_name.lower().strip().replace(" ", "_").replace("-", "_")
252
+ )
253
+
254
+ # Check if name is valid (not too long, no special chars, doesn't already exist)
255
+ max_retries = 3
256
+ for retry in range(max_retries):
257
+ issues = []
258
+
259
+ # Check if folder already exists
260
+ if os.path.exists(project_name):
261
+ issues.append(f"folder '{project_name}' already exists")
262
+
263
+ # Check if name is too long
264
+ if len(project_name) > 30:
265
+ issues.append(
266
+ f"name too long ({len(project_name)} chars, max 30)"
267
+ )
268
+
269
+ # Check if name has invalid characters
270
+ if not project_name.replace("_", "").replace(".", "").isalnum():
271
+ issues.append(
272
+ "name contains invalid characters (only a-z, 0-9, _ allowed)"
273
+ )
274
+
275
+ # Check if name is empty
276
+ if not project_name or project_name == "_":
277
+ issues.append("name is empty or invalid")
278
+
279
+ # If valid, break
280
+ if not issues:
281
+ break
282
+
283
+ # Ask LLM to fix the name
284
+ if retry < max_retries - 1:
285
+ fix_prompt = f"""The project name "{project_name}" has issues: {', '.join(issues)}
286
+
287
+ Please provide a new, valid project name that:
288
+ - Is short and descriptive (max 30 characters)
289
+ - Uses snake_case (lowercase with underscores)
290
+ - Doesn't already exist
291
+ - Only contains letters, numbers, and underscores
292
+
293
+ Original project description: {query}
294
+
295
+ Respond with ONLY the new project name, nothing else."""
296
+
297
+ new_name = self.chat.send(
298
+ fix_prompt, max_tokens=50
299
+ ).text.strip()
300
+ # Clean the response
301
+ new_name = (
302
+ new_name.lower()
303
+ .strip()
304
+ .replace(" ", "_")
305
+ .replace("-", "_")
306
+ )
307
+ # Remove quotes if present
308
+ new_name = new_name.strip('"').strip("'")
309
+
310
+ self.console.print_warning(
311
+ f"⚠️ Project name '{project_name}' invalid: {', '.join(issues)}"
312
+ )
313
+ self.console.print_info(f" Retrying with: '{new_name}'")
314
+ project_name = new_name
315
+ else:
316
+ # Last resort - generate unique name
317
+ import random
318
+
319
+ project_name = f"project_{random.randint(1000, 9999)}"
320
+ self.console.print_warning(
321
+ f"⚠️ Using fallback name: '{project_name}'"
322
+ )
323
+ break
324
+
325
+ modules = plan_data.get("modules", [])
326
+ test_modules = plan_data.get("tests", [])
327
+ architecture = plan_data.get("architecture", {})
328
+
329
+ # Ensure we have at least basic files
330
+ if not any(m["name"] == "requirements.txt" for m in modules):
331
+ modules.append(
332
+ {
333
+ "name": "requirements.txt",
334
+ "purpose": "Python dependencies",
335
+ }
336
+ )
337
+ if not any(m["name"] == "README.md" for m in modules):
338
+ modules.append(
339
+ {"name": "README.md", "purpose": "Project documentation"}
340
+ )
341
+
342
+ except (json.JSONDecodeError, TypeError) as e:
343
+ self.console.print_warning(
344
+ f"⚠️ Could not parse JSON plan, using fallback: {str(e)[:100]}"
345
+ )
346
+ # Fallback plan
347
+ project_name = "my_project"
348
+ modules = [
349
+ {"name": "main.py", "purpose": "Entry point"},
350
+ {"name": "core.py", "purpose": "Core logic"},
351
+ {"name": "requirements.txt", "purpose": "Dependencies"},
352
+ {"name": "README.md", "purpose": "Documentation"},
353
+ ]
354
+ test_modules = [{"name": "test_main.py", "coverage": "main tests"}]
355
+ architecture = {"overview": query}
356
+
357
+ # Create project directory
358
+ os.makedirs(project_name, exist_ok=True)
359
+ created_files = []
360
+ implementation_issues = []
361
+ test_results = {}
362
+
363
+ # Write detailed PLAN.md with architecture and outlines
364
+ plan_content = f"""# {project_name} - Architectural Plan
365
+
366
+ ## Project Overview
367
+ {architecture.get('overview', query)}
368
+
369
+ ## Architecture
370
+ - **Patterns**: {', '.join(architecture.get('patterns', ['Modular']))}
371
+ - **Technologies**: {', '.join(architecture.get('technologies', ['Python']))}
372
+
373
+ ## Implementation Tasks
374
+
375
+ ### Phase 1: Project Setup
376
+ - [ ] Create project structure
377
+ - [ ] Generate PLAN.md
378
+ - [ ] Set up requirements.txt
379
+
380
+ ### Phase 2: Core Modules
381
+ """
382
+ for module in modules:
383
+ plan_content += f"- [ ] Implement `{module['name']}` - {module.get('purpose', 'Core functionality')}\n"
384
+
385
+ plan_content += "\n### Phase 3: Test Suite\n"
386
+ for test in test_modules:
387
+ plan_content += f"- [ ] Generate `{test['name']}` - {test.get('coverage', 'Unit tests')}\n"
388
+
389
+ plan_content += """
390
+ ### Phase 4: Quality Assurance
391
+ - [ ] Run all tests
392
+ - [ ] Fix any test failures
393
+ - [ ] Apply Black formatting
394
+ - [ ] Fix linting issues
395
+
396
+ ### Phase 5: Documentation
397
+ - [ ] Verify README.md completeness
398
+ - [ ] Add usage examples
399
+ - [ ] Document API if applicable
400
+
401
+ ## Module Specifications
402
+ """
403
+ for module in modules:
404
+ plan_content += f"\n### {module['name']}\n"
405
+ plan_content += (
406
+ f"**Purpose**: {module.get('purpose', 'Implementation')}\n\n"
407
+ )
408
+
409
+ if module.get("classes"):
410
+ plan_content += "**Classes**:\n"
411
+ for cls in module["classes"]:
412
+ plan_content += (
413
+ f"- `{cls['name']}`: {cls.get('purpose', '')}\n"
414
+ )
415
+ if cls.get("methods"):
416
+ plan_content += (
417
+ f" - Methods: {', '.join(cls['methods'])}\n"
418
+ )
419
+
420
+ if module.get("functions"):
421
+ plan_content += "\n**Functions**:\n"
422
+ for func in module["functions"]:
423
+ plan_content += f"- `{func.get('signature', func['name'])}`: {func.get('purpose', '')}\n"
424
+
425
+ plan_content += "\n## Test Coverage\n"
426
+ for test in test_modules:
427
+ plan_content += (
428
+ f"- **{test['name']}**: {test.get('coverage', 'Tests')}\n"
429
+ )
430
+
431
+ plan_content += "\n## Implementation Order\n"
432
+ plan_content += "1. Core modules and data structures\n"
433
+ plan_content += "2. Business logic implementation\n"
434
+ plan_content += "3. Integration and API layers\n"
435
+ plan_content += "4. Comprehensive test suite\n"
436
+ plan_content += "5. Documentation and examples\n"
437
+
438
+ # Start streaming preview for PLAN.md (markdown file)
439
+ self.console.start_file_preview(
440
+ "PLAN.md", max_lines=20, title_prefix="📋"
441
+ )
442
+
443
+ # Stream the content in chunks for visual effect
444
+ plan_chunks = []
445
+ chunk_size = 500 # Characters per chunk
446
+ for i in range(0, len(plan_content), chunk_size):
447
+ chunk = plan_content[i : i + chunk_size]
448
+ plan_chunks.append(chunk)
449
+ self.console.update_file_preview(chunk)
450
+ # Small delay for visual streaming effect
451
+ import time
452
+
453
+ time.sleep(0.05)
454
+
455
+ # End the preview
456
+ self.console.stop_file_preview()
457
+
458
+ # Write PLAN.md
459
+ plan_path = os.path.join(project_name, "PLAN.md")
460
+ with open(plan_path, "w", encoding="utf-8") as f:
461
+ f.write(plan_content)
462
+ created_files.append(plan_path)
463
+
464
+ plan_lines = plan_content.count("\n") + 1
465
+ self.console.print_success(
466
+ f"✅ Created detailed PLAN.md with architecture ({plan_lines} lines)"
467
+ )
468
+
469
+ # Project setup phase complete - agent can now use update_plan_progress tool
470
+
471
+ # Phase 2: Implement files one-by-one with validation
472
+ self.console.print_info(
473
+ "\n📝 Phase 2: Implementing modules one-by-one..."
474
+ )
475
+
476
+ # Sort modules to implement core/config files first
477
+ priority_order = ["config", "models", "database", "utils", "core"]
478
+ modules_sorted = sorted(
479
+ modules,
480
+ key=lambda m: (
481
+ 0
482
+ if "requirements.txt" in m["name"]
483
+ else (
484
+ 1
485
+ if any(p in m["name"].lower() for p in priority_order)
486
+ else 2 if "main.py" in m["name"] else 3
487
+ )
488
+ ),
489
+ )
490
+
491
+ # Initial load of PLAN.md
492
+ plan_path = os.path.join(project_name, "PLAN.md")
493
+
494
+ for i, module in enumerate(modules_sorted, 1):
495
+ filename = module["name"]
496
+ progress_pct = int((i / len(modules)) * 100)
497
+ self.console.print_info(
498
+ f" [{i}/{len(modules)}] ({progress_pct}%) Generating {filename}..."
499
+ )
500
+
501
+ file_path = os.path.join(project_name, filename)
502
+
503
+ # Create subdirectories if needed
504
+ dir_path = os.path.dirname(file_path)
505
+ if dir_path:
506
+ os.makedirs(dir_path, exist_ok=True)
507
+
508
+ # Re-read PLAN.md each time to get latest updates
509
+ if os.path.exists(plan_path):
510
+ with open(plan_path, "r", encoding="utf-8") as f:
511
+ plan_context = f.read()
512
+ else:
513
+ plan_context = plan_content
514
+
515
+ # Generate with architecture context including latest PLAN.md
516
+ context = (
517
+ f"{query}\n\nProject Plan (Current State):\n{plan_context}\n\n"
518
+ f"Current Module:\n{json.dumps(module, indent=2)}"
519
+ )
520
+ code = self._generate_code_for_file(
521
+ filename=filename,
522
+ purpose=module.get("purpose", ""),
523
+ context=context,
524
+ )
525
+
526
+ # Write initial version
527
+ with open(file_path, "w", encoding="utf-8") as f:
528
+ f.write(code)
529
+
530
+ # Validate and fix iteratively (max 3 attempts)
531
+ if filename.endswith(".py"):
532
+ for attempt in range(3):
533
+ try:
534
+ # Syntax check
535
+
536
+ ast.parse(code)
537
+
538
+ # Anti-pattern check
539
+ antipattern_result = self._check_antipatterns(
540
+ Path(file_path), code
541
+ )
542
+ if antipattern_result["errors"]:
543
+ self.console.print_warning(
544
+ f" ⚠️ Anti-patterns detected: {len(antipattern_result['errors'])} issues"
545
+ )
546
+ implementation_issues.append(
547
+ {
548
+ "file": filename,
549
+ "type": "antipattern",
550
+ "issues": antipattern_result["errors"][:2],
551
+ }
552
+ )
553
+
554
+ # If syntax is valid, we're done
555
+ self.console.print_success(
556
+ f" ✅ {filename} validated"
557
+ )
558
+ break
559
+
560
+ except SyntaxError as e:
561
+ if attempt < 2:
562
+ self.console.print_warning(
563
+ f" 🔧 Fixing syntax error (attempt {attempt+1}/3)"
564
+ )
565
+ code = self._fix_code_with_llm(
566
+ code, file_path, str(e)
567
+ )
568
+ with open(file_path, "w", encoding="utf-8") as f:
569
+ f.write(code)
570
+ else:
571
+ self.console.print_error(
572
+ f" ❌ Could not fix syntax in {filename}"
573
+ )
574
+ implementation_issues.append(
575
+ {
576
+ "file": filename,
577
+ "type": "syntax",
578
+ "error": str(e),
579
+ }
580
+ )
581
+
582
+ created_files.append(file_path)
583
+
584
+ # Phase 3: Generate comprehensive tests
585
+ self.console.print_info("\n🧪 Phase 3: Generating test suite...")
586
+
587
+ for i, test in enumerate(test_modules, 1):
588
+ test_filename = test["name"]
589
+ progress_pct = int((i / len(test_modules)) * 100)
590
+ self.console.print_info(
591
+ f" [{i}/{len(test_modules)}] ({progress_pct}%) Generating {test_filename}..."
592
+ )
593
+
594
+ test_path = os.path.join(project_name, test_filename)
595
+
596
+ # Re-read PLAN.md to get latest updates
597
+ if os.path.exists(plan_path):
598
+ with open(plan_path, "r", encoding="utf-8") as f:
599
+ plan_context = f.read()
600
+ else:
601
+ plan_context = plan_content
602
+
603
+ # Generate test with context about what to test and latest PLAN.md
604
+ test_context = f"{query}\n\nProject Plan (Current State):\n{plan_context}\n\nTest Coverage: {test.get('coverage', '')}\n\nModules to test:\n"
605
+ for module in modules:
606
+ if not module["name"].startswith("test_"):
607
+ test_context += (
608
+ f"- {module['name']}: {module.get('purpose', '')}\n"
609
+ )
610
+
611
+ # Add timeout handling for test generation
612
+ try:
613
+ import threading
614
+
615
+ test_code = None
616
+ generation_error = None
617
+
618
+ def generate_with_timeout(
619
+ test_filename_param, test_param, test_context_param
620
+ ):
621
+ nonlocal test_code, generation_error
622
+ try:
623
+ test_code = self._generate_code_for_file(
624
+ filename=test_filename_param,
625
+ purpose=f"Unit tests for {test_param.get('coverage', 'functionality')}",
626
+ context=test_context_param,
627
+ )
628
+ except Exception as e:
629
+ generation_error = e
630
+
631
+ # Run generation in a thread with timeout
632
+ gen_thread = threading.Thread(
633
+ target=generate_with_timeout,
634
+ args=(test_filename, test, test_context),
635
+ )
636
+ gen_thread.daemon = True
637
+ gen_thread.start()
638
+ gen_thread.join(
639
+ timeout=180
640
+ ) # 180 second (3 min) timeout for test generation
641
+
642
+ if gen_thread.is_alive():
643
+ self.console.print_warning(
644
+ f" ⚠️ Test generation timeout for {test_filename}, using placeholder..."
645
+ )
646
+ # Generate a simple placeholder test
647
+ test_code = f'''"""Unit tests for {test.get('coverage', 'functionality')}."""
648
+ import unittest
649
+
650
+ class TestPlaceholder(unittest.TestCase):
651
+ """Placeholder tests - generation timed out."""
652
+
653
+ def test_placeholder(self):
654
+ """Placeholder test - needs implementation."""
655
+ self.skipTest("Test generation timed out - needs manual implementation")
656
+
657
+ if __name__ == "__main__":
658
+ unittest.main()
659
+ '''
660
+ elif generation_error is not None:
661
+ if isinstance(generation_error, Exception):
662
+ raise generation_error
663
+ else:
664
+ raise Exception(
665
+ f"Test generation error: {generation_error}"
666
+ )
667
+ elif not test_code:
668
+ raise Exception("No test code generated")
669
+
670
+ except Exception as e:
671
+ self.console.print_warning(
672
+ f" ⚠️ Failed to generate {test_filename}: {str(e)[:100]}"
673
+ )
674
+ # Generate a simple placeholder test
675
+ test_code = f'''"""Unit tests for {test.get('coverage', 'functionality')}."""
676
+ import unittest
677
+
678
+ class TestPlaceholder(unittest.TestCase):
679
+ """Placeholder tests - generation failed."""
680
+
681
+ def test_placeholder(self):
682
+ """Placeholder test - needs implementation."""
683
+ self.skipTest("Test generation failed - needs manual implementation")
684
+
685
+ if __name__ == "__main__":
686
+ unittest.main()
687
+ '''
688
+
689
+ with open(test_path, "w", encoding="utf-8") as f:
690
+ f.write(test_code)
691
+
692
+ # Validate test file syntax
693
+ try:
694
+
695
+ ast.parse(test_code)
696
+ self.console.print_success(f" ✅ {test_filename} validated")
697
+ except SyntaxError as e:
698
+ self.console.print_warning(
699
+ f" ⚠️ Syntax issues in {test_filename}, attempting fix..."
700
+ )
701
+ test_code = self._fix_code_with_llm(
702
+ test_code, test_path, str(e)
703
+ )
704
+ with open(test_path, "w", encoding="utf-8") as f:
705
+ f.write(test_code)
706
+
707
+ created_files.append(test_path)
708
+
709
+ # Phase 3.5: Apply Black formatting to all Python files
710
+ self.console.print_info(
711
+ "\n🎨 Phase 3.5: Applying Black formatting to all Python files..."
712
+ )
713
+
714
+ # Format all Python files in the project
715
+ python_files = []
716
+ for f in created_files:
717
+ file_path = Path(f) if isinstance(f, str) else f
718
+ if file_path.suffix == ".py":
719
+ python_files.append(file_path)
720
+
721
+ formatted_count = 0
722
+
723
+ for py_file in python_files:
724
+ if py_file.exists():
725
+ format_result = self._execute_tool(
726
+ "format_with_black", {"file_path": str(py_file)}
727
+ )
728
+ if format_result.get("formatted"):
729
+ formatted_count += 1
730
+
731
+ if formatted_count > 0:
732
+ self.console.print_success(
733
+ f"✅ Formatted {formatted_count} Python file(s) with Black"
734
+ )
735
+ else:
736
+ self.console.print_info(
737
+ "✓ All Python files already properly formatted"
738
+ )
739
+
740
+ # Phase 4: Run tests and fix issues
741
+ self.console.print_info(
742
+ "\n🏃 Phase 4: Running tests and fixing issues..."
743
+ )
744
+
745
+ test_run_result = self._execute_tool(
746
+ "run_tests", {"project_path": project_name, "timeout": 30}
747
+ )
748
+ if test_run_result.get("status") == "success":
749
+ if test_run_result.get("tests_passed"):
750
+ self.console.print_success("✅ All tests passed!")
751
+ test_results["status"] = "passed"
752
+ test_results["details"] = "All tests executed successfully"
753
+ else:
754
+ # Show test failure details
755
+ failure_summary = test_run_result.get("failure_summary", "")
756
+ stdout = test_run_result.get("stdout", "")
757
+
758
+ self.console.print_warning(
759
+ f"⚠️ Some tests failed: {failure_summary}"
760
+ if failure_summary
761
+ else "⚠️ Some tests failed, attempting fixes..."
762
+ )
763
+
764
+ # Extract and show failed test names from pytest output
765
+ if stdout:
766
+ import re
767
+
768
+ # Get command that was run
769
+ test_command = test_run_result.get("command", "pytest")
770
+
771
+ # Look for FAILED lines in pytest output
772
+ failed_tests = re.findall(r"FAILED (.*?) -", stdout)
773
+ if failed_tests:
774
+ self.console.print_info(
775
+ f"\n Failed tests ({len(failed_tests)}):"
776
+ )
777
+ for test in failed_tests[:5]: # Show first 5
778
+ self.console.print_info(f" • {test}")
779
+ if len(failed_tests) > 5:
780
+ self.console.print_info(
781
+ f" ... and {len(failed_tests) - 5} more"
782
+ )
783
+
784
+ # Show terminal output preview - just raw output in a panel
785
+ # Take first 20 lines of pytest output
786
+ lines = stdout.split("\n")[:20]
787
+ if lines:
788
+ if (
789
+ hasattr(self.console, "console")
790
+ and self.console.console
791
+ ):
792
+ from rich.panel import Panel
793
+
794
+ # Show command and raw output
795
+ preview_text = f"$ {test_command}\n\n" + "\n".join(
796
+ lines
797
+ )
798
+ self.console.console.print(
799
+ Panel(
800
+ preview_text,
801
+ title="Test Output Preview",
802
+ border_style="yellow",
803
+ expand=False,
804
+ )
805
+ )
806
+ else:
807
+ print("\n Test Output Preview:")
808
+ print(" " + "─" * 70)
809
+ print(f" $ {test_command}\n")
810
+ for line in lines:
811
+ print(f" {line}")
812
+ print(" " + "─" * 70)
813
+
814
+ test_results["status"] = "partial"
815
+ test_results["stderr"] = test_run_result.get("stderr", "")
816
+ test_results["stdout"] = stdout
817
+ test_results["failure_summary"] = failure_summary
818
+
819
+ # Try to fix test failures
820
+ for attempt in range(2):
821
+ self.console.print_info(
822
+ f" 🔧 Fix attempt {attempt+1}/2..."
823
+ )
824
+
825
+ # Run auto_fix_syntax_errors on the project
826
+ fix_result = self._execute_tool(
827
+ "auto_fix_syntax_errors", {"project_path": project_name}
828
+ )
829
+ if fix_result.get("files_fixed"):
830
+ fixed_files = fix_result["files_fixed"]
831
+ self.console.print_info(
832
+ f" Fixed {len(fixed_files)} files:"
833
+ )
834
+ for file in fixed_files[:3]: # Show first 3
835
+ self.console.print_info(f" • {file}")
836
+ if len(fixed_files) > 3:
837
+ self.console.print_info(
838
+ f" ... and {len(fixed_files) - 3} more"
839
+ )
840
+
841
+ # Re-run tests
842
+ self.console.print_info(" Re-running tests...")
843
+ test_run_result = self._execute_tool(
844
+ "run_tests",
845
+ {"project_path": project_name, "timeout": 30},
846
+ )
847
+ if test_run_result.get("tests_passed"):
848
+ self.console.print_success(
849
+ " ✅ Tests now passing!"
850
+ )
851
+ test_results["status"] = "passed"
852
+ break
853
+ else:
854
+ # Show what's still failing
855
+ new_failure_summary = test_run_result.get(
856
+ "failure_summary", ""
857
+ )
858
+ if new_failure_summary:
859
+ self.console.print_warning(
860
+ f" Still failing: {new_failure_summary}"
861
+ )
862
+ else:
863
+ self.console.print_info(
864
+ " No syntax errors found to fix"
865
+ )
866
+ else:
867
+ self.console.print_warning(
868
+ f"⚠️ Could not run tests: {test_run_result.get('error', 'Unknown error')}"
869
+ )
870
+ test_results["status"] = "error"
871
+ test_results["error"] = test_run_result.get("error", "")
872
+
873
+ # Phase 5: Final comprehensive validation
874
+ self.console.print_info("\n🔍 Phase 5: Final project validation...")
875
+
876
+ final_validation = self._execute_tool(
877
+ "validate_project", {"project_path": project_name, "fix": True}
878
+ )
879
+
880
+ # Try to fix any remaining issues
881
+ if not final_validation.get("is_valid"):
882
+ self.console.print_info(" 🔧 Attempting final fixes...")
883
+
884
+ for attempt in range(2):
885
+ if final_validation.get("total_errors", 0) == 0:
886
+ break
887
+
888
+ # Run auto-fix
889
+ auto_fix_result = self._execute_tool(
890
+ "auto_fix_syntax_errors", {"project_path": project_name}
891
+ )
892
+ if auto_fix_result.get("files_fixed"):
893
+ self.console.print_info(
894
+ f" Fixed {len(auto_fix_result['files_fixed'])} files"
895
+ )
896
+
897
+ # Re-validate
898
+ final_validation = self._execute_tool(
899
+ "validate_project",
900
+ {"project_path": project_name, "fix": True},
901
+ )
902
+ if final_validation.get("is_valid"):
903
+ self.console.print_success(
904
+ "✅ All validation checks passed!"
905
+ )
906
+ break
907
+
908
+ # Phase 6: Generate summary
909
+ self.console.print_header("\n📊 Project Generation Complete!")
910
+
911
+ # Build summary message
912
+ summary = f"""
913
+ ## Project: {project_name}
914
+
915
+ ### 📋 Architecture
916
+ - **Overview**: {architecture.get('overview', query)[:100]}...
917
+ - **Technologies**: {', '.join(architecture.get('technologies', ['Python']))}
918
+ - **Patterns**: {', '.join(architecture.get('patterns', ['Modular']))}
919
+
920
+ ### 📁 Generated Files ({len(created_files)} total)
921
+ - **Core Modules**: {len([f for f in created_files if f.endswith('.py') and 'test' not in f])}
922
+ - **Test Files**: {len([f for f in created_files if 'test' in f])}
923
+ - **Documentation**: {len([f for f in created_files if f.endswith('.md')])}
924
+ - **Configuration**: {len([f for f in created_files if f.endswith(('.txt', '.yml', '.yaml', '.json'))])}
925
+
926
+ ### ✅ Quality Metrics
927
+ - **Syntax Validation**: {'✅ Passed' if not implementation_issues else f'⚠️ {len(implementation_issues)} issues'}
928
+ - **Test Results**: {test_results.get('status', 'Not run').title()}
929
+ - **Code Quality**: {final_validation.get('total_errors', 0)} errors, {final_validation.get('total_warnings', 0)} warnings
930
+ - **Anti-patterns**: {'None detected' if not any(i['type'] == 'antipattern' for i in implementation_issues) else 'Some detected'}
931
+
932
+ ### 🎯 Ready to Use
933
+ The project is structured and ready for development. Key features:
934
+ {chr(10).join(f"- {module['name']}: {module.get('purpose', '')}" for module in modules[:5])}
935
+
936
+ ### 🚀 Next Steps
937
+ 1. Review PLAN.md for architecture details
938
+ 2. Install dependencies: `pip install -r requirements.txt`
939
+ 3. Run the application: `python main.py`
940
+ 4. Run tests: `pytest`
941
+ """
942
+
943
+ if hasattr(self.console, "console") and self.console.console:
944
+ from rich.panel import Panel
945
+
946
+ self.console.console.print(
947
+ Panel(summary, title="Project Summary", expand=False)
948
+ )
949
+ else:
950
+ print(summary)
951
+
952
+ return {
953
+ "status": "success",
954
+ "project_name": project_name,
955
+ "files_created": created_files,
956
+ "validation": final_validation,
957
+ "test_results": test_results,
958
+ "implementation_issues": implementation_issues,
959
+ "summary": summary,
960
+ "message": f"✅ Successfully created {project_name} with {len(created_files)} files",
961
+ }
962
+
963
+ except Exception as e:
964
+ return {"status": "error", "error": str(e)}
965
+
966
+ def _validate_project_structure(
967
+ self, _project_path: Path, files: List[Path]
968
+ ) -> Dict[str, Any]:
969
+ """Validate project structure for consistency issues.
970
+
971
+ Args:
972
+ _project_path: Path to the project directory (unused currently)
973
+ files: List of Path objects for all files in the project
974
+
975
+ Returns:
976
+ Dictionary with validation results including errors and warnings
977
+ """
978
+ errors = []
979
+ warnings = []
980
+
981
+ filenames = [f.name for f in files if f.is_file()]
982
+
983
+ # Check for multiple entry points (common issue)
984
+ entry_points = ["main.py", "app.py", "run.py", "__main__.py", "wsgi.py"]
985
+ found_entries = [ep for ep in entry_points if ep in filenames]
986
+
987
+ if len(found_entries) > 1:
988
+ errors.append(
989
+ f"Multiple entry points found: {', '.join(found_entries)}. "
990
+ "Choose ONE pattern: either monolithic or modular."
991
+ )
992
+
993
+ # Check for essential files
994
+ if "README.md" not in filenames and "readme.md" not in filenames:
995
+ errors.append("Missing README.md")
996
+
997
+ if "requirements.txt" not in filenames and "pyproject.toml" not in filenames:
998
+ warnings.append("Missing requirements.txt or pyproject.toml")
999
+
1000
+ # Check for PLAN.md
1001
+ if "PLAN.md" not in filenames and "plan.md" not in filenames:
1002
+ warnings.append(
1003
+ "No PLAN.md found (architectural plan should be created first)"
1004
+ )
1005
+
1006
+ # Check for duplicate models (common issue)
1007
+ model_files = [
1008
+ f for f in files if "model" in f.name.lower() and f.suffix == ".py"
1009
+ ]
1010
+ if len(model_files) > 2:
1011
+ warnings.append(
1012
+ f"Multiple model files found ({len(model_files)}). "
1013
+ "Check for duplicate definitions."
1014
+ )
1015
+
1016
+ return {"is_valid": len(errors) == 0, "errors": errors, "warnings": warnings}