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,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}")