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,391 +1,391 @@
|
|
|
1
|
-
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
"""Project Analyzer for Understanding Current Project State.
|
|
4
|
-
|
|
5
|
-
This module analyzes the current state of a project directory to provide
|
|
6
|
-
context for the LLM during checklist generation. It detects:
|
|
7
|
-
- Whether the project exists
|
|
8
|
-
- What framework/tools are configured
|
|
9
|
-
- What models/routes/pages already exist
|
|
10
|
-
|
|
11
|
-
This information helps the LLM generate a checklist that:
|
|
12
|
-
- Doesn't recreate things that already exist
|
|
13
|
-
- Builds on existing infrastructure
|
|
14
|
-
- Fills in missing pieces
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
import json
|
|
18
|
-
import logging
|
|
19
|
-
import re
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
from typing import List
|
|
22
|
-
|
|
23
|
-
from .checklist_generator import ProjectState
|
|
24
|
-
|
|
25
|
-
logger = logging.getLogger(__name__)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class ProjectAnalyzer:
|
|
29
|
-
"""Analyze project state for LLM context.
|
|
30
|
-
|
|
31
|
-
Provides detailed information about what exists in a project
|
|
32
|
-
so the LLM can generate appropriate checklists.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def analyze(self, project_dir: str) -> ProjectState:
|
|
36
|
-
"""Analyze a project directory.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
project_dir: Path to project directory
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
ProjectState with analysis results
|
|
43
|
-
"""
|
|
44
|
-
project_path = Path(project_dir)
|
|
45
|
-
|
|
46
|
-
if not project_path.exists():
|
|
47
|
-
logger.info(f"Project directory does not exist: {project_dir}")
|
|
48
|
-
return ProjectState(exists=False)
|
|
49
|
-
|
|
50
|
-
state = ProjectState(exists=True)
|
|
51
|
-
|
|
52
|
-
# Check for package.json (Node.js project)
|
|
53
|
-
package_json = project_path / "package.json"
|
|
54
|
-
if package_json.exists():
|
|
55
|
-
state.has_package_json = True
|
|
56
|
-
self._analyze_package_json(package_json, state)
|
|
57
|
-
|
|
58
|
-
# Check for Next.js config
|
|
59
|
-
next_config = project_path / "next.config.ts"
|
|
60
|
-
if not next_config.exists():
|
|
61
|
-
next_config = project_path / "next.config.js"
|
|
62
|
-
if not next_config.exists():
|
|
63
|
-
next_config = project_path / "next.config.mjs"
|
|
64
|
-
state.has_next_config = next_config.exists()
|
|
65
|
-
|
|
66
|
-
# Check for Prisma
|
|
67
|
-
prisma_schema = project_path / "prisma" / "schema.prisma"
|
|
68
|
-
if prisma_schema.exists():
|
|
69
|
-
state.has_prisma = True
|
|
70
|
-
state.existing_models = self._analyze_prisma_schema(prisma_schema)
|
|
71
|
-
|
|
72
|
-
# Analyze existing routes
|
|
73
|
-
api_dir = project_path / "src" / "app" / "api"
|
|
74
|
-
if api_dir.exists():
|
|
75
|
-
state.existing_routes = self._analyze_api_routes(api_dir)
|
|
76
|
-
|
|
77
|
-
# Analyze existing pages
|
|
78
|
-
app_dir = project_path / "src" / "app"
|
|
79
|
-
if app_dir.exists():
|
|
80
|
-
state.existing_pages = self._analyze_pages(app_dir)
|
|
81
|
-
|
|
82
|
-
logger.debug(f"Project analysis complete: {state.to_prompt()}")
|
|
83
|
-
return state
|
|
84
|
-
|
|
85
|
-
def _analyze_package_json(self, package_path: Path, state: ProjectState) -> None:
|
|
86
|
-
"""Analyze package.json for dependencies.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
package_path: Path to package.json
|
|
90
|
-
state: ProjectState to update
|
|
91
|
-
"""
|
|
92
|
-
try:
|
|
93
|
-
content = json.loads(package_path.read_text())
|
|
94
|
-
deps = content.get("dependencies", {})
|
|
95
|
-
dev_deps = content.get("devDependencies", {})
|
|
96
|
-
all_deps = {**deps, **dev_deps}
|
|
97
|
-
|
|
98
|
-
# Check for common dependencies
|
|
99
|
-
if "prisma" in all_deps or "@prisma/client" in all_deps:
|
|
100
|
-
state.has_prisma = True
|
|
101
|
-
|
|
102
|
-
except json.JSONDecodeError:
|
|
103
|
-
logger.warning(f"Could not parse package.json: {package_path}")
|
|
104
|
-
|
|
105
|
-
def _analyze_prisma_schema(self, schema_path: Path) -> List[str]:
|
|
106
|
-
"""Extract model names from Prisma schema.
|
|
107
|
-
|
|
108
|
-
Args:
|
|
109
|
-
schema_path: Path to schema.prisma
|
|
110
|
-
|
|
111
|
-
Returns:
|
|
112
|
-
List of model names
|
|
113
|
-
"""
|
|
114
|
-
models = []
|
|
115
|
-
try:
|
|
116
|
-
content = schema_path.read_text()
|
|
117
|
-
|
|
118
|
-
# Find all model definitions
|
|
119
|
-
model_pattern = r"model\s+(\w+)\s*\{"
|
|
120
|
-
matches = re.findall(model_pattern, content)
|
|
121
|
-
models = list(matches)
|
|
122
|
-
|
|
123
|
-
logger.debug(f"Found Prisma models: {models}")
|
|
124
|
-
|
|
125
|
-
except Exception as e:
|
|
126
|
-
logger.warning(f"Could not parse Prisma schema: {e}")
|
|
127
|
-
|
|
128
|
-
return models
|
|
129
|
-
|
|
130
|
-
def _analyze_api_routes(self, api_dir: Path) -> List[str]:
|
|
131
|
-
"""Find existing API routes.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
api_dir: Path to src/app/api directory
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
List of route paths (e.g., ["/todos", "/users"])
|
|
138
|
-
"""
|
|
139
|
-
routes = []
|
|
140
|
-
|
|
141
|
-
try:
|
|
142
|
-
for route_file in api_dir.rglob("route.ts"):
|
|
143
|
-
# Extract route path from file location
|
|
144
|
-
relative = route_file.relative_to(api_dir)
|
|
145
|
-
parts = list(relative.parts[:-1]) # Remove "route.ts"
|
|
146
|
-
|
|
147
|
-
if parts:
|
|
148
|
-
route_path = "/" + "/".join(parts)
|
|
149
|
-
routes.append(route_path)
|
|
150
|
-
|
|
151
|
-
logger.debug(f"Found API routes: {routes}")
|
|
152
|
-
|
|
153
|
-
except Exception as e:
|
|
154
|
-
logger.warning(f"Could not analyze API routes: {e}")
|
|
155
|
-
|
|
156
|
-
return routes
|
|
157
|
-
|
|
158
|
-
def _analyze_pages(self, app_dir: Path) -> List[str]:
|
|
159
|
-
"""Find existing pages.
|
|
160
|
-
|
|
161
|
-
Args:
|
|
162
|
-
app_dir: Path to src/app directory
|
|
163
|
-
|
|
164
|
-
Returns:
|
|
165
|
-
List of page paths (e.g., ["/", "/todos", "/todos/new"])
|
|
166
|
-
"""
|
|
167
|
-
pages = []
|
|
168
|
-
|
|
169
|
-
try:
|
|
170
|
-
for page_file in app_dir.rglob("page.tsx"):
|
|
171
|
-
# Extract page path from file location
|
|
172
|
-
relative = page_file.relative_to(app_dir)
|
|
173
|
-
parts = list(relative.parts[:-1]) # Remove "page.tsx"
|
|
174
|
-
|
|
175
|
-
if not parts:
|
|
176
|
-
pages.append("/")
|
|
177
|
-
else:
|
|
178
|
-
# Skip API routes
|
|
179
|
-
if parts[0] == "api":
|
|
180
|
-
continue
|
|
181
|
-
page_path = "/" + "/".join(parts)
|
|
182
|
-
pages.append(page_path)
|
|
183
|
-
|
|
184
|
-
logger.debug(f"Found pages: {pages}")
|
|
185
|
-
|
|
186
|
-
except Exception as e:
|
|
187
|
-
logger.warning(f"Could not analyze pages: {e}")
|
|
188
|
-
|
|
189
|
-
return pages
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def analyze_project(project_dir: str) -> ProjectState:
|
|
193
|
-
"""Convenience function to analyze a project.
|
|
194
|
-
|
|
195
|
-
Args:
|
|
196
|
-
project_dir: Path to project directory
|
|
197
|
-
|
|
198
|
-
Returns:
|
|
199
|
-
ProjectState with analysis results
|
|
200
|
-
"""
|
|
201
|
-
analyzer = ProjectAnalyzer()
|
|
202
|
-
return analyzer.analyze(project_dir)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def get_missing_crud_parts(
|
|
206
|
-
state: ProjectState,
|
|
207
|
-
resource_name: str,
|
|
208
|
-
) -> List[str]:
|
|
209
|
-
"""Determine what CRUD parts are missing for a resource.
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
state: Current project state
|
|
213
|
-
resource_name: Resource to check (singular, lowercase)
|
|
214
|
-
|
|
215
|
-
Returns:
|
|
216
|
-
List of missing parts (e.g., ["api_collection", "list_page"])
|
|
217
|
-
"""
|
|
218
|
-
missing = []
|
|
219
|
-
resource_plural = resource_name + "s" # Simple pluralization
|
|
220
|
-
|
|
221
|
-
# Check model
|
|
222
|
-
model_name = resource_name.capitalize()
|
|
223
|
-
if model_name not in state.existing_models:
|
|
224
|
-
missing.append("prisma_model")
|
|
225
|
-
|
|
226
|
-
# Check API routes
|
|
227
|
-
collection_route = f"/{resource_plural}"
|
|
228
|
-
item_route = f"/{resource_plural}/[id]"
|
|
229
|
-
|
|
230
|
-
if collection_route not in state.existing_routes:
|
|
231
|
-
missing.append("api_collection")
|
|
232
|
-
if item_route not in state.existing_routes:
|
|
233
|
-
missing.append("api_item")
|
|
234
|
-
|
|
235
|
-
# Check pages
|
|
236
|
-
list_page = f"/{resource_plural}"
|
|
237
|
-
new_page = f"/{resource_plural}/new"
|
|
238
|
-
detail_page = f"/{resource_plural}/[id]"
|
|
239
|
-
|
|
240
|
-
if list_page not in state.existing_pages:
|
|
241
|
-
missing.append("list_page")
|
|
242
|
-
if new_page not in state.existing_pages:
|
|
243
|
-
missing.append("new_page")
|
|
244
|
-
if detail_page not in state.existing_pages:
|
|
245
|
-
missing.append("detail_page")
|
|
246
|
-
|
|
247
|
-
return missing
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def suggest_checklist_items(
|
|
251
|
-
state: ProjectState,
|
|
252
|
-
resource_name: str,
|
|
253
|
-
fields: dict,
|
|
254
|
-
) -> List[dict]:
|
|
255
|
-
"""Suggest checklist items based on project state.
|
|
256
|
-
|
|
257
|
-
This is a helper for testing/comparison with LLM-generated checklists.
|
|
258
|
-
|
|
259
|
-
Args:
|
|
260
|
-
state: Current project state
|
|
261
|
-
resource_name: Resource to create
|
|
262
|
-
fields: Field definitions for the resource
|
|
263
|
-
|
|
264
|
-
Returns:
|
|
265
|
-
List of suggested checklist item dictionaries
|
|
266
|
-
"""
|
|
267
|
-
items = []
|
|
268
|
-
resource = resource_name.lower()
|
|
269
|
-
Resource = resource_name.capitalize()
|
|
270
|
-
|
|
271
|
-
# Check what's missing
|
|
272
|
-
missing = get_missing_crud_parts(state, resource)
|
|
273
|
-
|
|
274
|
-
# Setup items (if project doesn't exist or is incomplete)
|
|
275
|
-
if not state.has_package_json:
|
|
276
|
-
items.append(
|
|
277
|
-
{
|
|
278
|
-
"template": "create_next_app",
|
|
279
|
-
"params": {"project_name": f"{resource}-app"},
|
|
280
|
-
"description": "Initialize Next.js project",
|
|
281
|
-
}
|
|
282
|
-
)
|
|
283
|
-
items.append(
|
|
284
|
-
{
|
|
285
|
-
"template": "setup_app_styling",
|
|
286
|
-
"params": {"app_title": f"{Resource} App"},
|
|
287
|
-
"description": "Configure modern styling",
|
|
288
|
-
}
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
if not state.has_prisma:
|
|
292
|
-
items.append(
|
|
293
|
-
{
|
|
294
|
-
"template": "setup_prisma",
|
|
295
|
-
"params": {},
|
|
296
|
-
"description": "Initialize Prisma ORM",
|
|
297
|
-
}
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
# Data model
|
|
301
|
-
if "prisma_model" in missing:
|
|
302
|
-
items.append(
|
|
303
|
-
{
|
|
304
|
-
"template": "generate_prisma_model",
|
|
305
|
-
"params": {"model_name": Resource, "fields": fields},
|
|
306
|
-
"description": f"Define {Resource} database model",
|
|
307
|
-
}
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
# API routes
|
|
311
|
-
if "api_collection" in missing:
|
|
312
|
-
items.append(
|
|
313
|
-
{
|
|
314
|
-
"template": "generate_api_route",
|
|
315
|
-
"params": {
|
|
316
|
-
"resource": resource,
|
|
317
|
-
"operations": ["GET", "POST"],
|
|
318
|
-
"type": "collection",
|
|
319
|
-
},
|
|
320
|
-
"description": f"Create API for listing and creating {resource}s",
|
|
321
|
-
}
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
if "api_item" in missing:
|
|
325
|
-
items.append(
|
|
326
|
-
{
|
|
327
|
-
"template": "generate_api_route",
|
|
328
|
-
"params": {
|
|
329
|
-
"resource": resource,
|
|
330
|
-
"operations": ["GET", "PATCH", "DELETE"],
|
|
331
|
-
"type": "item",
|
|
332
|
-
},
|
|
333
|
-
"description": f"Create API for single {resource} operations",
|
|
334
|
-
}
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
# UI components
|
|
338
|
-
if "list_page" in missing:
|
|
339
|
-
items.append(
|
|
340
|
-
{
|
|
341
|
-
"template": "generate_react_component",
|
|
342
|
-
"params": {"resource": resource, "variant": "list"},
|
|
343
|
-
"description": f"List page showing all {resource}s",
|
|
344
|
-
}
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
# Form component (always needed if any UI is missing)
|
|
348
|
-
if any(p in missing for p in ["new_page", "detail_page"]):
|
|
349
|
-
items.append(
|
|
350
|
-
{
|
|
351
|
-
"template": "generate_react_component",
|
|
352
|
-
"params": {"resource": resource, "variant": "form"},
|
|
353
|
-
"description": "Form component for create/edit",
|
|
354
|
-
}
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
if "new_page" in missing:
|
|
358
|
-
items.append(
|
|
359
|
-
{
|
|
360
|
-
"template": "generate_react_component",
|
|
361
|
-
"params": {"resource": resource, "variant": "new"},
|
|
362
|
-
"description": f"Create new {resource} page",
|
|
363
|
-
}
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
if "detail_page" in missing:
|
|
367
|
-
items.append(
|
|
368
|
-
{
|
|
369
|
-
"template": "generate_react_component",
|
|
370
|
-
"params": {"resource": resource, "variant": "detail"},
|
|
371
|
-
"description": f"View/edit {resource} page",
|
|
372
|
-
}
|
|
373
|
-
)
|
|
374
|
-
items.append(
|
|
375
|
-
{
|
|
376
|
-
"template": "generate_react_component",
|
|
377
|
-
"params": {"resource": resource, "variant": "actions"},
|
|
378
|
-
"description": "Edit/delete buttons component",
|
|
379
|
-
}
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
# Landing page update
|
|
383
|
-
items.append(
|
|
384
|
-
{
|
|
385
|
-
"template": "update_landing_page",
|
|
386
|
-
"params": {"resource": resource},
|
|
387
|
-
"description": f"Add navigation to {resource}s",
|
|
388
|
-
}
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
return items
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Project Analyzer for Understanding Current Project State.
|
|
4
|
+
|
|
5
|
+
This module analyzes the current state of a project directory to provide
|
|
6
|
+
context for the LLM during checklist generation. It detects:
|
|
7
|
+
- Whether the project exists
|
|
8
|
+
- What framework/tools are configured
|
|
9
|
+
- What models/routes/pages already exist
|
|
10
|
+
|
|
11
|
+
This information helps the LLM generate a checklist that:
|
|
12
|
+
- Doesn't recreate things that already exist
|
|
13
|
+
- Builds on existing infrastructure
|
|
14
|
+
- Fills in missing pieces
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import List
|
|
22
|
+
|
|
23
|
+
from .checklist_generator import ProjectState
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProjectAnalyzer:
|
|
29
|
+
"""Analyze project state for LLM context.
|
|
30
|
+
|
|
31
|
+
Provides detailed information about what exists in a project
|
|
32
|
+
so the LLM can generate appropriate checklists.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def analyze(self, project_dir: str) -> ProjectState:
|
|
36
|
+
"""Analyze a project directory.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
project_dir: Path to project directory
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
ProjectState with analysis results
|
|
43
|
+
"""
|
|
44
|
+
project_path = Path(project_dir)
|
|
45
|
+
|
|
46
|
+
if not project_path.exists():
|
|
47
|
+
logger.info(f"Project directory does not exist: {project_dir}")
|
|
48
|
+
return ProjectState(exists=False)
|
|
49
|
+
|
|
50
|
+
state = ProjectState(exists=True)
|
|
51
|
+
|
|
52
|
+
# Check for package.json (Node.js project)
|
|
53
|
+
package_json = project_path / "package.json"
|
|
54
|
+
if package_json.exists():
|
|
55
|
+
state.has_package_json = True
|
|
56
|
+
self._analyze_package_json(package_json, state)
|
|
57
|
+
|
|
58
|
+
# Check for Next.js config
|
|
59
|
+
next_config = project_path / "next.config.ts"
|
|
60
|
+
if not next_config.exists():
|
|
61
|
+
next_config = project_path / "next.config.js"
|
|
62
|
+
if not next_config.exists():
|
|
63
|
+
next_config = project_path / "next.config.mjs"
|
|
64
|
+
state.has_next_config = next_config.exists()
|
|
65
|
+
|
|
66
|
+
# Check for Prisma
|
|
67
|
+
prisma_schema = project_path / "prisma" / "schema.prisma"
|
|
68
|
+
if prisma_schema.exists():
|
|
69
|
+
state.has_prisma = True
|
|
70
|
+
state.existing_models = self._analyze_prisma_schema(prisma_schema)
|
|
71
|
+
|
|
72
|
+
# Analyze existing routes
|
|
73
|
+
api_dir = project_path / "src" / "app" / "api"
|
|
74
|
+
if api_dir.exists():
|
|
75
|
+
state.existing_routes = self._analyze_api_routes(api_dir)
|
|
76
|
+
|
|
77
|
+
# Analyze existing pages
|
|
78
|
+
app_dir = project_path / "src" / "app"
|
|
79
|
+
if app_dir.exists():
|
|
80
|
+
state.existing_pages = self._analyze_pages(app_dir)
|
|
81
|
+
|
|
82
|
+
logger.debug(f"Project analysis complete: {state.to_prompt()}")
|
|
83
|
+
return state
|
|
84
|
+
|
|
85
|
+
def _analyze_package_json(self, package_path: Path, state: ProjectState) -> None:
|
|
86
|
+
"""Analyze package.json for dependencies.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
package_path: Path to package.json
|
|
90
|
+
state: ProjectState to update
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
content = json.loads(package_path.read_text())
|
|
94
|
+
deps = content.get("dependencies", {})
|
|
95
|
+
dev_deps = content.get("devDependencies", {})
|
|
96
|
+
all_deps = {**deps, **dev_deps}
|
|
97
|
+
|
|
98
|
+
# Check for common dependencies
|
|
99
|
+
if "prisma" in all_deps or "@prisma/client" in all_deps:
|
|
100
|
+
state.has_prisma = True
|
|
101
|
+
|
|
102
|
+
except json.JSONDecodeError:
|
|
103
|
+
logger.warning(f"Could not parse package.json: {package_path}")
|
|
104
|
+
|
|
105
|
+
def _analyze_prisma_schema(self, schema_path: Path) -> List[str]:
|
|
106
|
+
"""Extract model names from Prisma schema.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
schema_path: Path to schema.prisma
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
List of model names
|
|
113
|
+
"""
|
|
114
|
+
models = []
|
|
115
|
+
try:
|
|
116
|
+
content = schema_path.read_text()
|
|
117
|
+
|
|
118
|
+
# Find all model definitions
|
|
119
|
+
model_pattern = r"model\s+(\w+)\s*\{"
|
|
120
|
+
matches = re.findall(model_pattern, content)
|
|
121
|
+
models = list(matches)
|
|
122
|
+
|
|
123
|
+
logger.debug(f"Found Prisma models: {models}")
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.warning(f"Could not parse Prisma schema: {e}")
|
|
127
|
+
|
|
128
|
+
return models
|
|
129
|
+
|
|
130
|
+
def _analyze_api_routes(self, api_dir: Path) -> List[str]:
|
|
131
|
+
"""Find existing API routes.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
api_dir: Path to src/app/api directory
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
List of route paths (e.g., ["/todos", "/users"])
|
|
138
|
+
"""
|
|
139
|
+
routes = []
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
for route_file in api_dir.rglob("route.ts"):
|
|
143
|
+
# Extract route path from file location
|
|
144
|
+
relative = route_file.relative_to(api_dir)
|
|
145
|
+
parts = list(relative.parts[:-1]) # Remove "route.ts"
|
|
146
|
+
|
|
147
|
+
if parts:
|
|
148
|
+
route_path = "/" + "/".join(parts)
|
|
149
|
+
routes.append(route_path)
|
|
150
|
+
|
|
151
|
+
logger.debug(f"Found API routes: {routes}")
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.warning(f"Could not analyze API routes: {e}")
|
|
155
|
+
|
|
156
|
+
return routes
|
|
157
|
+
|
|
158
|
+
def _analyze_pages(self, app_dir: Path) -> List[str]:
|
|
159
|
+
"""Find existing pages.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
app_dir: Path to src/app directory
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List of page paths (e.g., ["/", "/todos", "/todos/new"])
|
|
166
|
+
"""
|
|
167
|
+
pages = []
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
for page_file in app_dir.rglob("page.tsx"):
|
|
171
|
+
# Extract page path from file location
|
|
172
|
+
relative = page_file.relative_to(app_dir)
|
|
173
|
+
parts = list(relative.parts[:-1]) # Remove "page.tsx"
|
|
174
|
+
|
|
175
|
+
if not parts:
|
|
176
|
+
pages.append("/")
|
|
177
|
+
else:
|
|
178
|
+
# Skip API routes
|
|
179
|
+
if parts[0] == "api":
|
|
180
|
+
continue
|
|
181
|
+
page_path = "/" + "/".join(parts)
|
|
182
|
+
pages.append(page_path)
|
|
183
|
+
|
|
184
|
+
logger.debug(f"Found pages: {pages}")
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.warning(f"Could not analyze pages: {e}")
|
|
188
|
+
|
|
189
|
+
return pages
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def analyze_project(project_dir: str) -> ProjectState:
|
|
193
|
+
"""Convenience function to analyze a project.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
project_dir: Path to project directory
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
ProjectState with analysis results
|
|
200
|
+
"""
|
|
201
|
+
analyzer = ProjectAnalyzer()
|
|
202
|
+
return analyzer.analyze(project_dir)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_missing_crud_parts(
|
|
206
|
+
state: ProjectState,
|
|
207
|
+
resource_name: str,
|
|
208
|
+
) -> List[str]:
|
|
209
|
+
"""Determine what CRUD parts are missing for a resource.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
state: Current project state
|
|
213
|
+
resource_name: Resource to check (singular, lowercase)
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
List of missing parts (e.g., ["api_collection", "list_page"])
|
|
217
|
+
"""
|
|
218
|
+
missing = []
|
|
219
|
+
resource_plural = resource_name + "s" # Simple pluralization
|
|
220
|
+
|
|
221
|
+
# Check model
|
|
222
|
+
model_name = resource_name.capitalize()
|
|
223
|
+
if model_name not in state.existing_models:
|
|
224
|
+
missing.append("prisma_model")
|
|
225
|
+
|
|
226
|
+
# Check API routes
|
|
227
|
+
collection_route = f"/{resource_plural}"
|
|
228
|
+
item_route = f"/{resource_plural}/[id]"
|
|
229
|
+
|
|
230
|
+
if collection_route not in state.existing_routes:
|
|
231
|
+
missing.append("api_collection")
|
|
232
|
+
if item_route not in state.existing_routes:
|
|
233
|
+
missing.append("api_item")
|
|
234
|
+
|
|
235
|
+
# Check pages
|
|
236
|
+
list_page = f"/{resource_plural}"
|
|
237
|
+
new_page = f"/{resource_plural}/new"
|
|
238
|
+
detail_page = f"/{resource_plural}/[id]"
|
|
239
|
+
|
|
240
|
+
if list_page not in state.existing_pages:
|
|
241
|
+
missing.append("list_page")
|
|
242
|
+
if new_page not in state.existing_pages:
|
|
243
|
+
missing.append("new_page")
|
|
244
|
+
if detail_page not in state.existing_pages:
|
|
245
|
+
missing.append("detail_page")
|
|
246
|
+
|
|
247
|
+
return missing
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def suggest_checklist_items(
|
|
251
|
+
state: ProjectState,
|
|
252
|
+
resource_name: str,
|
|
253
|
+
fields: dict,
|
|
254
|
+
) -> List[dict]:
|
|
255
|
+
"""Suggest checklist items based on project state.
|
|
256
|
+
|
|
257
|
+
This is a helper for testing/comparison with LLM-generated checklists.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
state: Current project state
|
|
261
|
+
resource_name: Resource to create
|
|
262
|
+
fields: Field definitions for the resource
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
List of suggested checklist item dictionaries
|
|
266
|
+
"""
|
|
267
|
+
items = []
|
|
268
|
+
resource = resource_name.lower()
|
|
269
|
+
Resource = resource_name.capitalize()
|
|
270
|
+
|
|
271
|
+
# Check what's missing
|
|
272
|
+
missing = get_missing_crud_parts(state, resource)
|
|
273
|
+
|
|
274
|
+
# Setup items (if project doesn't exist or is incomplete)
|
|
275
|
+
if not state.has_package_json:
|
|
276
|
+
items.append(
|
|
277
|
+
{
|
|
278
|
+
"template": "create_next_app",
|
|
279
|
+
"params": {"project_name": f"{resource}-app"},
|
|
280
|
+
"description": "Initialize Next.js project",
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
items.append(
|
|
284
|
+
{
|
|
285
|
+
"template": "setup_app_styling",
|
|
286
|
+
"params": {"app_title": f"{Resource} App"},
|
|
287
|
+
"description": "Configure modern styling",
|
|
288
|
+
}
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if not state.has_prisma:
|
|
292
|
+
items.append(
|
|
293
|
+
{
|
|
294
|
+
"template": "setup_prisma",
|
|
295
|
+
"params": {},
|
|
296
|
+
"description": "Initialize Prisma ORM",
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Data model
|
|
301
|
+
if "prisma_model" in missing:
|
|
302
|
+
items.append(
|
|
303
|
+
{
|
|
304
|
+
"template": "generate_prisma_model",
|
|
305
|
+
"params": {"model_name": Resource, "fields": fields},
|
|
306
|
+
"description": f"Define {Resource} database model",
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# API routes
|
|
311
|
+
if "api_collection" in missing:
|
|
312
|
+
items.append(
|
|
313
|
+
{
|
|
314
|
+
"template": "generate_api_route",
|
|
315
|
+
"params": {
|
|
316
|
+
"resource": resource,
|
|
317
|
+
"operations": ["GET", "POST"],
|
|
318
|
+
"type": "collection",
|
|
319
|
+
},
|
|
320
|
+
"description": f"Create API for listing and creating {resource}s",
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if "api_item" in missing:
|
|
325
|
+
items.append(
|
|
326
|
+
{
|
|
327
|
+
"template": "generate_api_route",
|
|
328
|
+
"params": {
|
|
329
|
+
"resource": resource,
|
|
330
|
+
"operations": ["GET", "PATCH", "DELETE"],
|
|
331
|
+
"type": "item",
|
|
332
|
+
},
|
|
333
|
+
"description": f"Create API for single {resource} operations",
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# UI components
|
|
338
|
+
if "list_page" in missing:
|
|
339
|
+
items.append(
|
|
340
|
+
{
|
|
341
|
+
"template": "generate_react_component",
|
|
342
|
+
"params": {"resource": resource, "variant": "list"},
|
|
343
|
+
"description": f"List page showing all {resource}s",
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# Form component (always needed if any UI is missing)
|
|
348
|
+
if any(p in missing for p in ["new_page", "detail_page"]):
|
|
349
|
+
items.append(
|
|
350
|
+
{
|
|
351
|
+
"template": "generate_react_component",
|
|
352
|
+
"params": {"resource": resource, "variant": "form"},
|
|
353
|
+
"description": "Form component for create/edit",
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if "new_page" in missing:
|
|
358
|
+
items.append(
|
|
359
|
+
{
|
|
360
|
+
"template": "generate_react_component",
|
|
361
|
+
"params": {"resource": resource, "variant": "new"},
|
|
362
|
+
"description": f"Create new {resource} page",
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
if "detail_page" in missing:
|
|
367
|
+
items.append(
|
|
368
|
+
{
|
|
369
|
+
"template": "generate_react_component",
|
|
370
|
+
"params": {"resource": resource, "variant": "detail"},
|
|
371
|
+
"description": f"View/edit {resource} page",
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
items.append(
|
|
375
|
+
{
|
|
376
|
+
"template": "generate_react_component",
|
|
377
|
+
"params": {"resource": resource, "variant": "actions"},
|
|
378
|
+
"description": "Edit/delete buttons component",
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Landing page update
|
|
383
|
+
items.append(
|
|
384
|
+
{
|
|
385
|
+
"template": "update_landing_page",
|
|
386
|
+
"params": {"resource": resource},
|
|
387
|
+
"description": f"Add navigation to {resource}s",
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return items
|