diff --git a/agentstack/frameworks/agent_protocol.py b/agentstack/frameworks/agent_protocol.py index bbba7f5..8e589c4 100644 --- a/agentstack/frameworks/agent_protocol.py +++ b/agentstack/frameworks/agent_protocol.py @@ -60,24 +60,40 @@ def validate(self) -> None: elif target.id == 'tools': has_tools = True elif isinstance(node, ast.FunctionDef): - if hasattr(node, 'decorator_list'): - for decorator in node.decorator_list: - # Handle both simple decorators and attribute decorators - if isinstance(decorator, ast.Name) and decorator.id == 'on_task': - has_task_handler = True - elif isinstance(decorator, ast.Attribute): - if (isinstance(decorator.value, ast.Name) and - decorator.value.id == 'agent_protocol' and - decorator.attr == 'on_task'): - has_task_handler = True - # Similar check for on_step decorator - elif isinstance(decorator, ast.Name) and decorator.id == 'on_step': - has_step_handler = True - elif isinstance(decorator, ast.Attribute): - if (isinstance(decorator.value, ast.Name) and - decorator.value.id == 'agent_protocol' and - decorator.attr == 'on_step'): - has_step_handler = True + for decorator in node.decorator_list: + # Handle all possible decorator patterns + decorator_str = '' + if isinstance(decorator, ast.Name): + decorator_str = decorator.id + elif isinstance(decorator, ast.Attribute): + # Build full decorator path (e.g., agent_protocol.on_task) + parts = [] + current = decorator + while isinstance(current, ast.Attribute): + parts.append(current.attr) + current = current.value + if isinstance(current, ast.Name): + parts.append(current.id) + decorator_str = '.'.join(reversed(parts)) + elif isinstance(decorator, ast.Call): + # Handle decorator calls (e.g., @decorator()) + if isinstance(decorator.func, ast.Attribute): + parts = [] + current = decorator.func + while isinstance(current, ast.Attribute): + parts.append(current.attr) + current = current.value + if isinstance(current, ast.Name): + parts.append(current.id) + decorator_str = '.'.join(reversed(parts)) + elif isinstance(decorator.func, ast.Name): + decorator_str = decorator.func.id + + # Check for task and step handlers + if 'on_task' in decorator_str: + has_task_handler = True + elif 'on_step' in decorator_str: + has_step_handler = True if not has_app: raise ValidationError(f"FastAPI app not found in {self.path}") diff --git a/agentstack/generation/project_generation.py b/agentstack/generation/project_generation.py index 0600325..05d9eeb 100644 --- a/agentstack/generation/project_generation.py +++ b/agentstack/generation/project_generation.py @@ -23,18 +23,31 @@ def generate_project(project_dir: Path, framework: str, project_name: str, proje project_name: Human-readable name of the project project_slug: URL-friendly slug for the project """ - template_dir = get_package_path() / 'templates' / framework + # Get absolute path to template directory using package path + package_path = get_package_path() + template_dir = package_path / 'templates' / framework + # Validate template directory and cookiecutter.json existence if not template_dir.exists(): - raise ValidationError(f"Template directory for framework '{framework}' not found") + raise ValidationError( + f"Template directory for framework '{framework}' not found at {template_dir}. " + f"Package path: {package_path}" + ) + + cookiecutter_json = template_dir / 'cookiecutter.json' + if not cookiecutter_json.exists(): + raise ValidationError( + f"cookiecutter.json not found in template directory at {cookiecutter_json}. " + f"Directory contents: {list(template_dir.iterdir())}" + ) # Create project directory if it doesn't exist os.makedirs(project_dir, exist_ok=True) - # Generate project using cookiecutter + # Generate project using cookiecutter with absolute path cookiecutter( - str(template_dir), - output_dir=str(project_dir.parent), + str(template_dir.absolute()), + output_dir=str(project_dir.parent.absolute()), no_input=True, extra_context={ 'project_metadata': { @@ -48,5 +61,11 @@ def generate_project(project_dir: Path, framework: str, project_name: str, proje nested_dir = project_dir.parent / project_slug if nested_dir.exists() and nested_dir != project_dir: for item in nested_dir.iterdir(): - shutil.move(str(item), str(project_dir / item.name)) + target_path = project_dir / item.name + if target_path.exists(): + if target_path.is_dir(): + shutil.rmtree(target_path) + else: + target_path.unlink() + shutil.move(str(item), str(target_path)) nested_dir.rmdir() diff --git a/pyproject.toml b/pyproject.toml index 69f3c30..a68b0ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "wheel"] +requires = ["setuptools>=61.0.0", "wheel"] build-backend = "setuptools.build_meta" [project]