amd-gaia 0.14.3__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.
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
- amd_gaia-0.15.1.dist-info/RECORD +178 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2177 -2177
- gaia/agents/base/api_agent.py +120 -120
- gaia/agents/base/console.py +1841 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +83 -83
- gaia/agents/blender/agent.py +556 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +835 -835
- gaia/agents/chat/app.py +1058 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1729 -1729
- gaia/agents/chat/tools/shell_tools.py +436 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2036 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +642 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1506 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1974 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +173 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +430 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5481 -5621
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/llm/__init__.py +9 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3236 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +120 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +163 -163
- gaia/talk/app.py +289 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.14.3.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -729
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
- {amd_gaia-0.14.3.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}")
|