amd-gaia 0.15.0__py3-none-any.whl → 0.15.2__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 (185) hide show
  1. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +222 -223
  2. amd_gaia-0.15.2.dist-info/RECORD +182 -0
  3. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
  4. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +1 -0
  5. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +20 -20
  6. gaia/__init__.py +29 -29
  7. gaia/agents/__init__.py +19 -19
  8. gaia/agents/base/__init__.py +9 -9
  9. gaia/agents/base/agent.py +2132 -2177
  10. gaia/agents/base/api_agent.py +119 -120
  11. gaia/agents/base/console.py +1967 -1841
  12. gaia/agents/base/errors.py +237 -237
  13. gaia/agents/base/mcp_agent.py +86 -86
  14. gaia/agents/base/tools.py +88 -83
  15. gaia/agents/blender/__init__.py +7 -0
  16. gaia/agents/blender/agent.py +553 -556
  17. gaia/agents/blender/agent_simple.py +133 -135
  18. gaia/agents/blender/app.py +211 -211
  19. gaia/agents/blender/app_simple.py +41 -41
  20. gaia/agents/blender/core/__init__.py +16 -16
  21. gaia/agents/blender/core/materials.py +506 -506
  22. gaia/agents/blender/core/objects.py +316 -316
  23. gaia/agents/blender/core/rendering.py +225 -225
  24. gaia/agents/blender/core/scene.py +220 -220
  25. gaia/agents/blender/core/view.py +146 -146
  26. gaia/agents/chat/__init__.py +9 -9
  27. gaia/agents/chat/agent.py +809 -835
  28. gaia/agents/chat/app.py +1065 -1058
  29. gaia/agents/chat/session.py +508 -508
  30. gaia/agents/chat/tools/__init__.py +15 -15
  31. gaia/agents/chat/tools/file_tools.py +96 -96
  32. gaia/agents/chat/tools/rag_tools.py +1744 -1729
  33. gaia/agents/chat/tools/shell_tools.py +437 -436
  34. gaia/agents/code/__init__.py +7 -7
  35. gaia/agents/code/agent.py +549 -549
  36. gaia/agents/code/cli.py +377 -0
  37. gaia/agents/code/models.py +135 -135
  38. gaia/agents/code/orchestration/__init__.py +24 -24
  39. gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
  40. gaia/agents/code/orchestration/checklist_generator.py +713 -713
  41. gaia/agents/code/orchestration/factories/__init__.py +9 -9
  42. gaia/agents/code/orchestration/factories/base.py +63 -63
  43. gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
  44. gaia/agents/code/orchestration/factories/python_factory.py +106 -106
  45. gaia/agents/code/orchestration/orchestrator.py +841 -841
  46. gaia/agents/code/orchestration/project_analyzer.py +391 -391
  47. gaia/agents/code/orchestration/steps/__init__.py +67 -67
  48. gaia/agents/code/orchestration/steps/base.py +188 -188
  49. gaia/agents/code/orchestration/steps/error_handler.py +314 -314
  50. gaia/agents/code/orchestration/steps/nextjs.py +828 -828
  51. gaia/agents/code/orchestration/steps/python.py +307 -307
  52. gaia/agents/code/orchestration/template_catalog.py +469 -469
  53. gaia/agents/code/orchestration/workflows/__init__.py +14 -14
  54. gaia/agents/code/orchestration/workflows/base.py +80 -80
  55. gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
  56. gaia/agents/code/orchestration/workflows/python.py +94 -94
  57. gaia/agents/code/prompts/__init__.py +11 -11
  58. gaia/agents/code/prompts/base_prompt.py +77 -77
  59. gaia/agents/code/prompts/code_patterns.py +2034 -2036
  60. gaia/agents/code/prompts/nextjs_prompt.py +40 -40
  61. gaia/agents/code/prompts/python_prompt.py +109 -109
  62. gaia/agents/code/schema_inference.py +365 -365
  63. gaia/agents/code/system_prompt.py +41 -41
  64. gaia/agents/code/tools/__init__.py +42 -42
  65. gaia/agents/code/tools/cli_tools.py +1138 -1138
  66. gaia/agents/code/tools/code_formatting.py +319 -319
  67. gaia/agents/code/tools/code_tools.py +769 -769
  68. gaia/agents/code/tools/error_fixing.py +1347 -1347
  69. gaia/agents/code/tools/external_tools.py +180 -180
  70. gaia/agents/code/tools/file_io.py +845 -845
  71. gaia/agents/code/tools/prisma_tools.py +190 -190
  72. gaia/agents/code/tools/project_management.py +1016 -1016
  73. gaia/agents/code/tools/testing.py +321 -321
  74. gaia/agents/code/tools/typescript_tools.py +122 -122
  75. gaia/agents/code/tools/validation_parsing.py +461 -461
  76. gaia/agents/code/tools/validation_tools.py +806 -806
  77. gaia/agents/code/tools/web_dev_tools.py +1758 -1758
  78. gaia/agents/code/validators/__init__.py +16 -16
  79. gaia/agents/code/validators/antipattern_checker.py +241 -241
  80. gaia/agents/code/validators/ast_analyzer.py +197 -197
  81. gaia/agents/code/validators/requirements_validator.py +145 -145
  82. gaia/agents/code/validators/syntax_validator.py +171 -171
  83. gaia/agents/docker/__init__.py +7 -7
  84. gaia/agents/docker/agent.py +643 -642
  85. gaia/agents/emr/__init__.py +8 -8
  86. gaia/agents/emr/agent.py +1504 -1506
  87. gaia/agents/emr/cli.py +1322 -1322
  88. gaia/agents/emr/constants.py +475 -475
  89. gaia/agents/emr/dashboard/__init__.py +4 -4
  90. gaia/agents/emr/dashboard/server.py +1972 -1974
  91. gaia/agents/jira/__init__.py +11 -11
  92. gaia/agents/jira/agent.py +894 -894
  93. gaia/agents/jira/jql_templates.py +299 -299
  94. gaia/agents/routing/__init__.py +7 -7
  95. gaia/agents/routing/agent.py +567 -570
  96. gaia/agents/routing/system_prompt.py +75 -75
  97. gaia/agents/summarize/__init__.py +11 -0
  98. gaia/agents/summarize/agent.py +885 -0
  99. gaia/agents/summarize/prompts.py +129 -0
  100. gaia/api/__init__.py +23 -23
  101. gaia/api/agent_registry.py +238 -238
  102. gaia/api/app.py +305 -305
  103. gaia/api/openai_server.py +575 -575
  104. gaia/api/schemas.py +186 -186
  105. gaia/api/sse_handler.py +373 -373
  106. gaia/apps/__init__.py +4 -4
  107. gaia/apps/llm/__init__.py +6 -6
  108. gaia/apps/llm/app.py +184 -169
  109. gaia/apps/summarize/app.py +116 -633
  110. gaia/apps/summarize/html_viewer.py +133 -133
  111. gaia/apps/summarize/pdf_formatter.py +284 -284
  112. gaia/audio/__init__.py +2 -2
  113. gaia/audio/audio_client.py +439 -439
  114. gaia/audio/audio_recorder.py +269 -269
  115. gaia/audio/kokoro_tts.py +599 -599
  116. gaia/audio/whisper_asr.py +432 -432
  117. gaia/chat/__init__.py +16 -16
  118. gaia/chat/app.py +428 -430
  119. gaia/chat/prompts.py +522 -522
  120. gaia/chat/sdk.py +1228 -1225
  121. gaia/cli.py +5659 -5632
  122. gaia/database/__init__.py +10 -10
  123. gaia/database/agent.py +176 -176
  124. gaia/database/mixin.py +290 -290
  125. gaia/database/testing.py +64 -64
  126. gaia/eval/batch_experiment.py +2332 -2332
  127. gaia/eval/claude.py +542 -542
  128. gaia/eval/config.py +37 -37
  129. gaia/eval/email_generator.py +512 -512
  130. gaia/eval/eval.py +3179 -3179
  131. gaia/eval/groundtruth.py +1130 -1130
  132. gaia/eval/transcript_generator.py +582 -582
  133. gaia/eval/webapp/README.md +167 -167
  134. gaia/eval/webapp/package-lock.json +875 -875
  135. gaia/eval/webapp/package.json +20 -20
  136. gaia/eval/webapp/public/app.js +3402 -3402
  137. gaia/eval/webapp/public/index.html +87 -87
  138. gaia/eval/webapp/public/styles.css +3661 -3661
  139. gaia/eval/webapp/server.js +415 -415
  140. gaia/eval/webapp/test-setup.js +72 -72
  141. gaia/installer/__init__.py +23 -0
  142. gaia/installer/init_command.py +1275 -0
  143. gaia/installer/lemonade_installer.py +619 -0
  144. gaia/llm/__init__.py +10 -2
  145. gaia/llm/base_client.py +60 -0
  146. gaia/llm/exceptions.py +12 -0
  147. gaia/llm/factory.py +70 -0
  148. gaia/llm/lemonade_client.py +3421 -3221
  149. gaia/llm/lemonade_manager.py +294 -294
  150. gaia/llm/providers/__init__.py +9 -0
  151. gaia/llm/providers/claude.py +108 -0
  152. gaia/llm/providers/lemonade.py +118 -0
  153. gaia/llm/providers/openai_provider.py +79 -0
  154. gaia/llm/vlm_client.py +382 -382
  155. gaia/logger.py +189 -189
  156. gaia/mcp/agent_mcp_server.py +245 -245
  157. gaia/mcp/blender_mcp_client.py +138 -138
  158. gaia/mcp/blender_mcp_server.py +648 -648
  159. gaia/mcp/context7_cache.py +332 -332
  160. gaia/mcp/external_services.py +518 -518
  161. gaia/mcp/mcp_bridge.py +811 -550
  162. gaia/mcp/servers/__init__.py +6 -6
  163. gaia/mcp/servers/docker_mcp.py +83 -83
  164. gaia/perf_analysis.py +361 -0
  165. gaia/rag/__init__.py +10 -10
  166. gaia/rag/app.py +293 -293
  167. gaia/rag/demo.py +304 -304
  168. gaia/rag/pdf_utils.py +235 -235
  169. gaia/rag/sdk.py +2194 -2194
  170. gaia/security.py +183 -163
  171. gaia/talk/app.py +287 -289
  172. gaia/talk/sdk.py +538 -538
  173. gaia/testing/__init__.py +87 -87
  174. gaia/testing/assertions.py +330 -330
  175. gaia/testing/fixtures.py +333 -333
  176. gaia/testing/mocks.py +493 -493
  177. gaia/util.py +46 -46
  178. gaia/utils/__init__.py +33 -33
  179. gaia/utils/file_watcher.py +675 -675
  180. gaia/utils/parsing.py +223 -223
  181. gaia/version.py +100 -100
  182. amd_gaia-0.15.0.dist-info/RECORD +0 -168
  183. gaia/agents/code/app.py +0 -266
  184. gaia/llm/llm_client.py +0 -723
  185. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/top_level.txt +0 -0
@@ -1,461 +1,461 @@
1
- # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
- # SPDX-License-Identifier: MIT
3
- """Consolidated validation and parsing mixin combining validation, AST parsing, and error fixing helpers."""
4
-
5
- import ast
6
- import logging
7
- import re
8
- from pathlib import Path
9
- from typing import TYPE_CHECKING, Any, Dict, List
10
-
11
- from ..models import CodeSymbol, ParsedCode
12
-
13
- if TYPE_CHECKING:
14
- from ..validators import ( # noqa: F401
15
- AntipatternChecker,
16
- RequirementsValidator,
17
- SyntaxValidator,
18
- )
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- class ValidationAndParsingMixin:
24
- """Consolidated mixin providing validation, AST parsing, and error fixing helpers.
25
-
26
- Attributes (provided by CodeAgent):
27
- syntax_validator: SyntaxValidator instance
28
- antipattern_checker: AntipatternChecker instance
29
- requirements_validator: RequirementsValidator instance
30
-
31
- This mixin provides helper methods (not tools) for:
32
- - Python syntax validation
33
- - Python code parsing with AST
34
- - Requirements.txt validation (hallucination detection)
35
- - Anti-pattern detection (combinatorial functions, excessive parameters)
36
- - JavaScript/TypeScript, CSS, and HTML validation
37
-
38
- Helper methods (used by other mixins):
39
- - _validate_python_syntax: Validate Python code syntax
40
- - _parse_python_code: Parse Python code and extract structure
41
- - _validate_requirements: Check requirements.txt for issues
42
- - _validate_python_files: Validate Python files with pylint and anti-patterns
43
- - _check_antipatterns: Detect code smells and anti-patterns
44
- - _validate_javascript_files: Validate JS/TS files with ESLint
45
- - _validate_css_files: Basic CSS validation
46
- - _validate_html_files: Basic HTML validation
47
-
48
- Note: This mixin does not register tools, it provides helper methods.
49
- """
50
-
51
- # ============================================================
52
- # VALIDATION HELPER METHODS
53
- # ============================================================
54
-
55
- def _validate_requirements(self, req_file: Path, fix: bool) -> Dict[str, Any]:
56
- """Validate requirements.txt for hallucinated packages.
57
-
58
- Args:
59
- req_file: Path to requirements.txt file
60
- fix: Whether to auto-fix issues
61
-
62
- Returns:
63
- Dictionary with validation results
64
- """
65
- return self.requirements_validator.validate(req_file, fix)
66
-
67
- def _validate_python_files(
68
- self, py_files: List[Path], _fix: bool
69
- ) -> Dict[str, Any]:
70
- """Validate Python files for syntax and common issues.
71
-
72
- Args:
73
- py_files: List of Python file paths
74
- _fix: Whether to auto-fix issues (unused currently)
75
-
76
- Returns:
77
- Dictionary with validation results
78
- """
79
- errors = []
80
- warnings = []
81
-
82
- for py_file in py_files:
83
- try:
84
- content = py_file.read_text()
85
-
86
- # Validate syntax
87
- syntax_result = self.syntax_validator.validate_dict(content)
88
- if not syntax_result["is_valid"]:
89
- errors.extend(
90
- [f"{py_file}: {err}" for err in syntax_result.get("errors", [])]
91
- )
92
-
93
- # Check for antipatterns
94
- antipattern_result = self._check_antipatterns(py_file, content)
95
- warnings.extend(
96
- [
97
- f"{py_file}: {warn}"
98
- for warn in antipattern_result.get("warnings", [])
99
- ]
100
- )
101
-
102
- except Exception as e:
103
- errors.append(f"{py_file}: Failed to validate - {e}")
104
-
105
- return {"errors": errors, "warnings": warnings, "is_valid": len(errors) == 0}
106
-
107
- def _check_antipatterns(self, _file_path: Path, content: str) -> Dict[str, Any]:
108
- """Check for common antipatterns in Python code.
109
-
110
- Args:
111
- _file_path: Path to the file being checked (unused currently)
112
- content: File content
113
-
114
- Returns:
115
- Dictionary with antipattern check results
116
- """
117
- return self.antipattern_checker.check_dict(content)
118
-
119
- def _validate_python_syntax(self, code: str) -> Dict[str, Any]:
120
- """Validate Python code syntax (delegates to validator).
121
-
122
- Args:
123
- code: Python code to validate
124
-
125
- Returns:
126
- Dictionary with validation results
127
- """
128
- return self.syntax_validator.validate_dict(code)
129
-
130
- def _validate_javascript_files(
131
- self, js_files: List[Path], _fix: bool
132
- ) -> Dict[str, Any]:
133
- """Validate JavaScript files for syntax issues.
134
-
135
- Args:
136
- js_files: List of JavaScript file paths
137
- _fix: Whether to auto-fix issues (unused currently)
138
-
139
- Returns:
140
- Dictionary with validation results
141
- """
142
- warnings = []
143
-
144
- for js_file in js_files:
145
- try:
146
- content = js_file.read_text()
147
-
148
- # Basic syntax checks
149
- if "var " in content:
150
- warnings.append(f"{js_file}: Use 'const' or 'let' instead of 'var'")
151
- if "==" in content and "===" not in content:
152
- warnings.append(
153
- f"{js_file}: Use '===' instead of '==' for equality checks"
154
- )
155
-
156
- except Exception as e:
157
- warnings.append(f"{js_file}: Failed to read - {e}")
158
-
159
- return {"warnings": warnings, "is_valid": True} # Warnings don't invalidate
160
-
161
- def _validate_css_content(self, content: str, file_path: Path) -> Dict[str, Any]:
162
- """Validate CSS file content for type mismatches and syntax errors.
163
-
164
- Detects TypeScript/JavaScript code in CSS files (Issue #1002).
165
- This is a CRITICAL check - CSS files containing TS/JS code will break the app.
166
-
167
- Args:
168
- content: File content to validate
169
- file_path: Path to the file being validated
170
-
171
- Returns:
172
- Dictionary with errors (blocking), warnings, and is_valid flag
173
- """
174
- errors = []
175
- warnings = []
176
-
177
- # CRITICAL: Detect TypeScript/JavaScript code in CSS files
178
- # These patterns indicate wrong file content - always invalid
179
- typescript_indicators = [
180
- (r"^\s*import\s+.*from", "import statement"),
181
- (r"^\s*export\s+(default|const|function|class|async)", "export statement"),
182
- (r'"use client"|\'use client\'', "React client directive"),
183
- (r"^\s*interface\s+\w+", "TypeScript interface"),
184
- (r"^\s*type\s+\w+\s*=", "TypeScript type alias"),
185
- (r"^\s*const\s+\w+\s*[=:]", "const declaration"),
186
- (r"^\s*let\s+\w+\s*[=:]", "let declaration"),
187
- (r"^\s*function\s+\w+", "function declaration"),
188
- (r"^\s*async\s+function", "async function"),
189
- (r"<[A-Z][a-zA-Z]*[\s/>]", "JSX component tag"),
190
- (r"useState|useEffect|useRouter|usePathname", "React hook"),
191
- ]
192
-
193
- for pattern, description in typescript_indicators:
194
- if re.search(pattern, content, re.MULTILINE):
195
- errors.append(
196
- f"{file_path}: CRITICAL - CSS file contains {description}. "
197
- f"This file has TypeScript/JSX code instead of CSS."
198
- )
199
-
200
- # Check for balanced braces
201
- if content.count("{") != content.count("}"):
202
- errors.append(f"{file_path}: Mismatched braces in CSS")
203
-
204
- # Check for Tailwind directives in globals.css
205
- if "globals.css" in str(file_path):
206
- has_tailwind = "@tailwind" in content or '@import "tailwindcss' in content
207
- if not has_tailwind and len(content.strip()) > 50:
208
- warnings.append(
209
- f"{file_path}: Missing Tailwind directives (@tailwind base/components/utilities)"
210
- )
211
-
212
- return {
213
- "errors": errors,
214
- "warnings": warnings,
215
- "is_valid": len(errors) == 0,
216
- "file_path": str(file_path),
217
- }
218
-
219
- def _validate_css_files(self, css_files: List[Path]) -> Dict[str, Any]:
220
- """Validate CSS files for content type and syntax issues.
221
-
222
- This is an enhanced validator that catches TypeScript code in CSS files
223
- (Issue #1002) and returns is_valid: False when errors exist.
224
-
225
- Args:
226
- css_files: List of CSS file paths
227
-
228
- Returns:
229
- Dictionary with validation results - errors are BLOCKING
230
- """
231
- all_errors = []
232
- all_warnings = []
233
-
234
- for css_file in css_files:
235
- try:
236
- content = css_file.read_text()
237
- result = self._validate_css_content(content, css_file)
238
- all_errors.extend(result.get("errors", []))
239
- all_warnings.extend(result.get("warnings", []))
240
- except Exception as e:
241
- all_errors.append(f"{css_file}: Failed to read - {e}")
242
-
243
- # CRITICAL CHANGE: is_valid is False if there are errors
244
- return {
245
- "errors": all_errors,
246
- "warnings": all_warnings,
247
- "is_valid": len(all_errors) == 0,
248
- }
249
-
250
- def _validate_html_files(self, html_files: List[Path]) -> Dict[str, Any]:
251
- """Validate HTML files for basic structure.
252
-
253
- Args:
254
- html_files: List of HTML file paths
255
-
256
- Returns:
257
- Dictionary with validation results
258
- """
259
- warnings = []
260
-
261
- for html_file in html_files:
262
- try:
263
- content = html_file.read_text()
264
-
265
- # Basic structure checks
266
- if "<html" not in content.lower():
267
- warnings.append(f"{html_file}: Missing <html> tag")
268
- if "<body" not in content.lower():
269
- warnings.append(f"{html_file}: Missing <body> tag")
270
-
271
- except Exception as e:
272
- warnings.append(f"{html_file}: Failed to read - {e}")
273
-
274
- return {"warnings": warnings, "is_valid": True}
275
-
276
- # ============================================================
277
- # AST PARSING METHODS
278
- # ============================================================
279
-
280
- def _parse_python_code(self, code: str) -> ParsedCode:
281
- """Parse Python code using AST.
282
-
283
- Args:
284
- code: Python source code
285
-
286
- Returns:
287
- ParsedCode object with parsing results
288
- """
289
- result = ParsedCode()
290
- result.symbols = []
291
- result.imports = []
292
- result.errors = []
293
-
294
- try:
295
- tree = ast.parse(code)
296
- result.ast_tree = tree
297
- result.is_valid = True
298
-
299
- for node in ast.walk(tree):
300
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
301
- signature = self._get_function_signature(node)
302
- docstring = ast.get_docstring(node)
303
- result.symbols.append(
304
- CodeSymbol(
305
- name=node.name,
306
- type="function",
307
- line=node.lineno,
308
- signature=signature,
309
- docstring=docstring,
310
- )
311
- )
312
-
313
- elif isinstance(node, ast.ClassDef):
314
- docstring = ast.get_docstring(node)
315
- result.symbols.append(
316
- CodeSymbol(
317
- name=node.name,
318
- type="class",
319
- line=node.lineno,
320
- docstring=docstring,
321
- )
322
- )
323
-
324
- elif isinstance(node, ast.Import):
325
- for alias in node.names:
326
- import_name = alias.asname if alias.asname else alias.name
327
- result.imports.append(f"import {alias.name}")
328
- result.symbols.append(
329
- CodeSymbol(
330
- name=import_name, type="import", line=node.lineno
331
- )
332
- )
333
-
334
- elif isinstance(node, ast.ImportFrom):
335
- module = node.module if node.module else ""
336
- for alias in node.names:
337
- import_name = alias.asname if alias.asname else alias.name
338
- result.imports.append(f"from {module} import {alias.name}")
339
- result.symbols.append(
340
- CodeSymbol(
341
- name=import_name, type="import", line=node.lineno
342
- )
343
- )
344
-
345
- elif isinstance(node, ast.Assign):
346
- for target in node.targets:
347
- if isinstance(target, ast.Name) and isinstance(
348
- target.ctx, ast.Store
349
- ):
350
- if hasattr(node, "col_offset") and node.col_offset == 0:
351
- result.symbols.append(
352
- CodeSymbol(
353
- name=target.id,
354
- type="variable",
355
- line=node.lineno,
356
- )
357
- )
358
-
359
- except SyntaxError as e:
360
- result.is_valid = False
361
- result.errors.append(f"Syntax error at line {e.lineno}: {e.msg}")
362
- except Exception as e:
363
- result.is_valid = False
364
- result.errors.append(f"Parse error: {str(e)}")
365
-
366
- return result
367
-
368
- def _get_function_signature(
369
- self, node: ast.FunctionDef | ast.AsyncFunctionDef
370
- ) -> str:
371
- """Extract function signature from AST node.
372
-
373
- Args:
374
- node: AST FunctionDef or AsyncFunctionDef node
375
-
376
- Returns:
377
- Function signature as string
378
- """
379
- params = []
380
-
381
- for arg in node.args.args:
382
- param = arg.arg
383
- if arg.annotation:
384
- param += f": {ast.unparse(arg.annotation)}"
385
- params.append(param)
386
-
387
- if node.args.vararg:
388
- param = f"*{node.args.vararg.arg}"
389
- if node.args.vararg.annotation:
390
- param += f": {ast.unparse(node.args.vararg.annotation)}"
391
- params.append(param)
392
-
393
- if node.args.kwarg:
394
- param = f"**{node.args.kwarg.arg}"
395
- if node.args.kwarg.annotation:
396
- param += f": {ast.unparse(node.args.kwarg.annotation)}"
397
- params.append(param)
398
-
399
- signature = f"{node.name}({', '.join(params)})"
400
-
401
- if node.returns:
402
- signature += f" -> {ast.unparse(node.returns)}"
403
-
404
- return signature
405
-
406
- def _extract_python_files(
407
- self, project_path: Path, plan_data: Dict[str, Any]
408
- ) -> List[Path]:
409
- """Extract list of Python files from project structure.
410
-
411
- Args:
412
- project_path: Root path of the project
413
- plan_data: Project plan data containing structure
414
-
415
- Returns:
416
- List of Python file paths
417
- """
418
- py_files = []
419
-
420
- def extract_from_structure(structure, current_path=""):
421
- if isinstance(structure, dict):
422
- for key, value in structure.items():
423
- new_path = f"{current_path}/{key}" if current_path else key
424
- if isinstance(value, dict):
425
- extract_from_structure(value, new_path)
426
- elif key.endswith(".py"):
427
- py_files.append(project_path / new_path)
428
-
429
- if "structure" in plan_data:
430
- extract_from_structure(plan_data["structure"])
431
-
432
- return py_files
433
-
434
- def _update_plan_task(self, plan_path: str, task_name: str, completed: bool = True):
435
- """Update task status in PLAN.md file.
436
-
437
- Args:
438
- plan_path: Path to PLAN.md file
439
- task_name: Name of the task to update
440
- completed: Whether task is completed (True) or in progress (False)
441
- """
442
- try:
443
- plan_file = Path(plan_path)
444
- if not plan_file.exists():
445
- return
446
-
447
- # pylint: disable=unspecified-encoding
448
- content = plan_file.read_text()
449
-
450
- if completed:
451
- pattern = rf"- \[ \] {re.escape(task_name)}"
452
- replacement = f"- [x] {task_name}"
453
- else:
454
- pattern = rf"- \[ \] {re.escape(task_name)}"
455
- replacement = f"- [~] {task_name}"
456
-
457
- new_content = re.sub(pattern, replacement, content)
458
- plan_file.write_text(new_content)
459
-
460
- except Exception as e:
461
- logger.warning(f"Failed to update plan task: {e}")
1
+ # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
+ # SPDX-License-Identifier: MIT
3
+ """Consolidated validation and parsing mixin combining validation, AST parsing, and error fixing helpers."""
4
+
5
+ import ast
6
+ import logging
7
+ import re
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any, Dict, List
10
+
11
+ from ..models import CodeSymbol, ParsedCode
12
+
13
+ if TYPE_CHECKING:
14
+ from ..validators import ( # noqa: F401
15
+ AntipatternChecker,
16
+ RequirementsValidator,
17
+ SyntaxValidator,
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class ValidationAndParsingMixin:
24
+ """Consolidated mixin providing validation, AST parsing, and error fixing helpers.
25
+
26
+ Attributes (provided by CodeAgent):
27
+ syntax_validator: SyntaxValidator instance
28
+ antipattern_checker: AntipatternChecker instance
29
+ requirements_validator: RequirementsValidator instance
30
+
31
+ This mixin provides helper methods (not tools) for:
32
+ - Python syntax validation
33
+ - Python code parsing with AST
34
+ - Requirements.txt validation (hallucination detection)
35
+ - Anti-pattern detection (combinatorial functions, excessive parameters)
36
+ - JavaScript/TypeScript, CSS, and HTML validation
37
+
38
+ Helper methods (used by other mixins):
39
+ - _validate_python_syntax: Validate Python code syntax
40
+ - _parse_python_code: Parse Python code and extract structure
41
+ - _validate_requirements: Check requirements.txt for issues
42
+ - _validate_python_files: Validate Python files with pylint and anti-patterns
43
+ - _check_antipatterns: Detect code smells and anti-patterns
44
+ - _validate_javascript_files: Validate JS/TS files with ESLint
45
+ - _validate_css_files: Basic CSS validation
46
+ - _validate_html_files: Basic HTML validation
47
+
48
+ Note: This mixin does not register tools, it provides helper methods.
49
+ """
50
+
51
+ # ============================================================
52
+ # VALIDATION HELPER METHODS
53
+ # ============================================================
54
+
55
+ def _validate_requirements(self, req_file: Path, fix: bool) -> Dict[str, Any]:
56
+ """Validate requirements.txt for hallucinated packages.
57
+
58
+ Args:
59
+ req_file: Path to requirements.txt file
60
+ fix: Whether to auto-fix issues
61
+
62
+ Returns:
63
+ Dictionary with validation results
64
+ """
65
+ return self.requirements_validator.validate(req_file, fix)
66
+
67
+ def _validate_python_files(
68
+ self, py_files: List[Path], _fix: bool
69
+ ) -> Dict[str, Any]:
70
+ """Validate Python files for syntax and common issues.
71
+
72
+ Args:
73
+ py_files: List of Python file paths
74
+ _fix: Whether to auto-fix issues (unused currently)
75
+
76
+ Returns:
77
+ Dictionary with validation results
78
+ """
79
+ errors = []
80
+ warnings = []
81
+
82
+ for py_file in py_files:
83
+ try:
84
+ content = py_file.read_text()
85
+
86
+ # Validate syntax
87
+ syntax_result = self.syntax_validator.validate_dict(content)
88
+ if not syntax_result["is_valid"]:
89
+ errors.extend(
90
+ [f"{py_file}: {err}" for err in syntax_result.get("errors", [])]
91
+ )
92
+
93
+ # Check for antipatterns
94
+ antipattern_result = self._check_antipatterns(py_file, content)
95
+ warnings.extend(
96
+ [
97
+ f"{py_file}: {warn}"
98
+ for warn in antipattern_result.get("warnings", [])
99
+ ]
100
+ )
101
+
102
+ except Exception as e:
103
+ errors.append(f"{py_file}: Failed to validate - {e}")
104
+
105
+ return {"errors": errors, "warnings": warnings, "is_valid": len(errors) == 0}
106
+
107
+ def _check_antipatterns(self, _file_path: Path, content: str) -> Dict[str, Any]:
108
+ """Check for common antipatterns in Python code.
109
+
110
+ Args:
111
+ _file_path: Path to the file being checked (unused currently)
112
+ content: File content
113
+
114
+ Returns:
115
+ Dictionary with antipattern check results
116
+ """
117
+ return self.antipattern_checker.check_dict(content)
118
+
119
+ def _validate_python_syntax(self, code: str) -> Dict[str, Any]:
120
+ """Validate Python code syntax (delegates to validator).
121
+
122
+ Args:
123
+ code: Python code to validate
124
+
125
+ Returns:
126
+ Dictionary with validation results
127
+ """
128
+ return self.syntax_validator.validate_dict(code)
129
+
130
+ def _validate_javascript_files(
131
+ self, js_files: List[Path], _fix: bool
132
+ ) -> Dict[str, Any]:
133
+ """Validate JavaScript files for syntax issues.
134
+
135
+ Args:
136
+ js_files: List of JavaScript file paths
137
+ _fix: Whether to auto-fix issues (unused currently)
138
+
139
+ Returns:
140
+ Dictionary with validation results
141
+ """
142
+ warnings = []
143
+
144
+ for js_file in js_files:
145
+ try:
146
+ content = js_file.read_text()
147
+
148
+ # Basic syntax checks
149
+ if "var " in content:
150
+ warnings.append(f"{js_file}: Use 'const' or 'let' instead of 'var'")
151
+ if "==" in content and "===" not in content:
152
+ warnings.append(
153
+ f"{js_file}: Use '===' instead of '==' for equality checks"
154
+ )
155
+
156
+ except Exception as e:
157
+ warnings.append(f"{js_file}: Failed to read - {e}")
158
+
159
+ return {"warnings": warnings, "is_valid": True} # Warnings don't invalidate
160
+
161
+ def _validate_css_content(self, content: str, file_path: Path) -> Dict[str, Any]:
162
+ """Validate CSS file content for type mismatches and syntax errors.
163
+
164
+ Detects TypeScript/JavaScript code in CSS files (Issue #1002).
165
+ This is a CRITICAL check - CSS files containing TS/JS code will break the app.
166
+
167
+ Args:
168
+ content: File content to validate
169
+ file_path: Path to the file being validated
170
+
171
+ Returns:
172
+ Dictionary with errors (blocking), warnings, and is_valid flag
173
+ """
174
+ errors = []
175
+ warnings = []
176
+
177
+ # CRITICAL: Detect TypeScript/JavaScript code in CSS files
178
+ # These patterns indicate wrong file content - always invalid
179
+ typescript_indicators = [
180
+ (r"^\s*import\s+.*from", "import statement"),
181
+ (r"^\s*export\s+(default|const|function|class|async)", "export statement"),
182
+ (r'"use client"|\'use client\'', "React client directive"),
183
+ (r"^\s*interface\s+\w+", "TypeScript interface"),
184
+ (r"^\s*type\s+\w+\s*=", "TypeScript type alias"),
185
+ (r"^\s*const\s+\w+\s*[=:]", "const declaration"),
186
+ (r"^\s*let\s+\w+\s*[=:]", "let declaration"),
187
+ (r"^\s*function\s+\w+", "function declaration"),
188
+ (r"^\s*async\s+function", "async function"),
189
+ (r"<[A-Z][a-zA-Z]*[\s/>]", "JSX component tag"),
190
+ (r"useState|useEffect|useRouter|usePathname", "React hook"),
191
+ ]
192
+
193
+ for pattern, description in typescript_indicators:
194
+ if re.search(pattern, content, re.MULTILINE):
195
+ errors.append(
196
+ f"{file_path}: CRITICAL - CSS file contains {description}. "
197
+ f"This file has TypeScript/JSX code instead of CSS."
198
+ )
199
+
200
+ # Check for balanced braces
201
+ if content.count("{") != content.count("}"):
202
+ errors.append(f"{file_path}: Mismatched braces in CSS")
203
+
204
+ # Check for Tailwind directives in globals.css
205
+ if "globals.css" in str(file_path):
206
+ has_tailwind = "@tailwind" in content or '@import "tailwindcss' in content
207
+ if not has_tailwind and len(content.strip()) > 50:
208
+ warnings.append(
209
+ f"{file_path}: Missing Tailwind directives (@tailwind base/components/utilities)"
210
+ )
211
+
212
+ return {
213
+ "errors": errors,
214
+ "warnings": warnings,
215
+ "is_valid": len(errors) == 0,
216
+ "file_path": str(file_path),
217
+ }
218
+
219
+ def _validate_css_files(self, css_files: List[Path]) -> Dict[str, Any]:
220
+ """Validate CSS files for content type and syntax issues.
221
+
222
+ This is an enhanced validator that catches TypeScript code in CSS files
223
+ (Issue #1002) and returns is_valid: False when errors exist.
224
+
225
+ Args:
226
+ css_files: List of CSS file paths
227
+
228
+ Returns:
229
+ Dictionary with validation results - errors are BLOCKING
230
+ """
231
+ all_errors = []
232
+ all_warnings = []
233
+
234
+ for css_file in css_files:
235
+ try:
236
+ content = css_file.read_text()
237
+ result = self._validate_css_content(content, css_file)
238
+ all_errors.extend(result.get("errors", []))
239
+ all_warnings.extend(result.get("warnings", []))
240
+ except Exception as e:
241
+ all_errors.append(f"{css_file}: Failed to read - {e}")
242
+
243
+ # CRITICAL CHANGE: is_valid is False if there are errors
244
+ return {
245
+ "errors": all_errors,
246
+ "warnings": all_warnings,
247
+ "is_valid": len(all_errors) == 0,
248
+ }
249
+
250
+ def _validate_html_files(self, html_files: List[Path]) -> Dict[str, Any]:
251
+ """Validate HTML files for basic structure.
252
+
253
+ Args:
254
+ html_files: List of HTML file paths
255
+
256
+ Returns:
257
+ Dictionary with validation results
258
+ """
259
+ warnings = []
260
+
261
+ for html_file in html_files:
262
+ try:
263
+ content = html_file.read_text()
264
+
265
+ # Basic structure checks
266
+ if "<html" not in content.lower():
267
+ warnings.append(f"{html_file}: Missing <html> tag")
268
+ if "<body" not in content.lower():
269
+ warnings.append(f"{html_file}: Missing <body> tag")
270
+
271
+ except Exception as e:
272
+ warnings.append(f"{html_file}: Failed to read - {e}")
273
+
274
+ return {"warnings": warnings, "is_valid": True}
275
+
276
+ # ============================================================
277
+ # AST PARSING METHODS
278
+ # ============================================================
279
+
280
+ def _parse_python_code(self, code: str) -> ParsedCode:
281
+ """Parse Python code using AST.
282
+
283
+ Args:
284
+ code: Python source code
285
+
286
+ Returns:
287
+ ParsedCode object with parsing results
288
+ """
289
+ result = ParsedCode()
290
+ result.symbols = []
291
+ result.imports = []
292
+ result.errors = []
293
+
294
+ try:
295
+ tree = ast.parse(code)
296
+ result.ast_tree = tree
297
+ result.is_valid = True
298
+
299
+ for node in ast.walk(tree):
300
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
301
+ signature = self._get_function_signature(node)
302
+ docstring = ast.get_docstring(node)
303
+ result.symbols.append(
304
+ CodeSymbol(
305
+ name=node.name,
306
+ type="function",
307
+ line=node.lineno,
308
+ signature=signature,
309
+ docstring=docstring,
310
+ )
311
+ )
312
+
313
+ elif isinstance(node, ast.ClassDef):
314
+ docstring = ast.get_docstring(node)
315
+ result.symbols.append(
316
+ CodeSymbol(
317
+ name=node.name,
318
+ type="class",
319
+ line=node.lineno,
320
+ docstring=docstring,
321
+ )
322
+ )
323
+
324
+ elif isinstance(node, ast.Import):
325
+ for alias in node.names:
326
+ import_name = alias.asname if alias.asname else alias.name
327
+ result.imports.append(f"import {alias.name}")
328
+ result.symbols.append(
329
+ CodeSymbol(
330
+ name=import_name, type="import", line=node.lineno
331
+ )
332
+ )
333
+
334
+ elif isinstance(node, ast.ImportFrom):
335
+ module = node.module if node.module else ""
336
+ for alias in node.names:
337
+ import_name = alias.asname if alias.asname else alias.name
338
+ result.imports.append(f"from {module} import {alias.name}")
339
+ result.symbols.append(
340
+ CodeSymbol(
341
+ name=import_name, type="import", line=node.lineno
342
+ )
343
+ )
344
+
345
+ elif isinstance(node, ast.Assign):
346
+ for target in node.targets:
347
+ if isinstance(target, ast.Name) and isinstance(
348
+ target.ctx, ast.Store
349
+ ):
350
+ if hasattr(node, "col_offset") and node.col_offset == 0:
351
+ result.symbols.append(
352
+ CodeSymbol(
353
+ name=target.id,
354
+ type="variable",
355
+ line=node.lineno,
356
+ )
357
+ )
358
+
359
+ except SyntaxError as e:
360
+ result.is_valid = False
361
+ result.errors.append(f"Syntax error at line {e.lineno}: {e.msg}")
362
+ except Exception as e:
363
+ result.is_valid = False
364
+ result.errors.append(f"Parse error: {str(e)}")
365
+
366
+ return result
367
+
368
+ def _get_function_signature(
369
+ self, node: ast.FunctionDef | ast.AsyncFunctionDef
370
+ ) -> str:
371
+ """Extract function signature from AST node.
372
+
373
+ Args:
374
+ node: AST FunctionDef or AsyncFunctionDef node
375
+
376
+ Returns:
377
+ Function signature as string
378
+ """
379
+ params = []
380
+
381
+ for arg in node.args.args:
382
+ param = arg.arg
383
+ if arg.annotation:
384
+ param += f": {ast.unparse(arg.annotation)}"
385
+ params.append(param)
386
+
387
+ if node.args.vararg:
388
+ param = f"*{node.args.vararg.arg}"
389
+ if node.args.vararg.annotation:
390
+ param += f": {ast.unparse(node.args.vararg.annotation)}"
391
+ params.append(param)
392
+
393
+ if node.args.kwarg:
394
+ param = f"**{node.args.kwarg.arg}"
395
+ if node.args.kwarg.annotation:
396
+ param += f": {ast.unparse(node.args.kwarg.annotation)}"
397
+ params.append(param)
398
+
399
+ signature = f"{node.name}({', '.join(params)})"
400
+
401
+ if node.returns:
402
+ signature += f" -> {ast.unparse(node.returns)}"
403
+
404
+ return signature
405
+
406
+ def _extract_python_files(
407
+ self, project_path: Path, plan_data: Dict[str, Any]
408
+ ) -> List[Path]:
409
+ """Extract list of Python files from project structure.
410
+
411
+ Args:
412
+ project_path: Root path of the project
413
+ plan_data: Project plan data containing structure
414
+
415
+ Returns:
416
+ List of Python file paths
417
+ """
418
+ py_files = []
419
+
420
+ def extract_from_structure(structure, current_path=""):
421
+ if isinstance(structure, dict):
422
+ for key, value in structure.items():
423
+ new_path = f"{current_path}/{key}" if current_path else key
424
+ if isinstance(value, dict):
425
+ extract_from_structure(value, new_path)
426
+ elif key.endswith(".py"):
427
+ py_files.append(project_path / new_path)
428
+
429
+ if "structure" in plan_data:
430
+ extract_from_structure(plan_data["structure"])
431
+
432
+ return py_files
433
+
434
+ def _update_plan_task(self, plan_path: str, task_name: str, completed: bool = True):
435
+ """Update task status in PLAN.md file.
436
+
437
+ Args:
438
+ plan_path: Path to PLAN.md file
439
+ task_name: Name of the task to update
440
+ completed: Whether task is completed (True) or in progress (False)
441
+ """
442
+ try:
443
+ plan_file = Path(plan_path)
444
+ if not plan_file.exists():
445
+ return
446
+
447
+ # pylint: disable=unspecified-encoding
448
+ content = plan_file.read_text()
449
+
450
+ if completed:
451
+ pattern = rf"- \[ \] {re.escape(task_name)}"
452
+ replacement = f"- [x] {task_name}"
453
+ else:
454
+ pattern = rf"- \[ \] {re.escape(task_name)}"
455
+ replacement = f"- [~] {task_name}"
456
+
457
+ new_content = re.sub(pattern, replacement, content)
458
+ plan_file.write_text(new_content)
459
+
460
+ except Exception as e:
461
+ logger.warning(f"Failed to update plan task: {e}")