"""Tool: create/read/update/validate project manifests (DB-backed, replaces .a0/manifests/*.json).""" from __future__ import annotations import json import os from typing import Any, Dict from helpers.tool import Tool, Response from usr.plugins.a0_software_orchestrator.helpers.db_state_store import get_project_id, get_kv, set_kv, list_kv_keys from usr.plugins.a0_software_orchestrator.helpers.manifest_schema import PROJECT_MANIFEST_SCHEMA, DEPLOYMENT_MANIFEST_SCHEMA from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError def _resolve_name(project_name: str = "", project_root: str = "") -> str: if project_name: return project_name.strip() if project_root: return os.path.basename(project_root.rstrip("/")) return os.path.basename(os.getcwd()) def _schema_for(manifest_type: str) -> Dict[str, Any]: if manifest_type == "project": return PROJECT_MANIFEST_SCHEMA if manifest_type == "deployment": return DEPLOYMENT_MANIFEST_SCHEMA return {} def _validate(data: Dict[str, Any], schema: Dict[str, Any]) -> list[str]: """Lightweight required-keys validator (subset of JSON-Schema).""" errors = [] if not isinstance(data, dict): return [f"manifest must be a JSON object, got {type(data).__name__}"] required = schema.get("required", []) for key in required: if key not in data: errors.append(f"missing required field: {key!r}") return errors class RepoManifest(Tool): """Create, read, update, list or validate project/deployment/quality manifests. Args (kwargs): project_name (str): Projektname (bevorzugt). project_root (str): Legacy – wird zu basename() aufgelöst. manifest_type (str): "project" (default), "deployment", "quality". action (str): "create" (default), "read", "update", "list", "validate". data (str|dict): JSON-string oder dict (für create/update). """ async def execute( self, project_name: str = "", project_root: str = "", manifest_type: str = "project", action: str = "create", data: str = "", **kwargs, ): name = _resolve_name(project_name, project_root) try: pid = get_project_id(name) if pid is None: raise LookupError(f"project '{name}' is not registered") except (ValueError, LookupError) as e: return Response(message=f"ERROR: {e}", break_loop=False) except PluginDBError as e: return Response(message=f"Database error: {e}", break_loop=False) key = f"manifest_{manifest_type}" schema = _schema_for(manifest_type) if action == "list": keys = [k for k in list_kv_keys(pid) if k.startswith("manifest_")] if not keys: return Response( message=f"No manifests for project {name!r}.", break_loop=False, ) return Response( message=f"Manifests for {name!r}: {', '.join(keys)}", break_loop=False, ) if action == "read": manifest = get_kv(pid, key, default=None) if manifest is None: return Response( message=f"No {manifest_type!r} manifest for project {name!r}.", break_loop=False, ) return Response( message=json.dumps(manifest, indent=2, ensure_ascii=False), break_loop=False, ) if action in ("create", "update"): # Parse data if not data: return Response( message="ERROR: 'data' required for create/update (JSON string or dict)", break_loop=False, ) if isinstance(data, str): try: parsed = json.loads(data) except json.JSONDecodeError as e: return Response( message=f"ERROR: 'data' is not valid JSON: {e}", break_loop=False, ) else: parsed = data # Validate against schema (if known) if schema: errors = _validate(parsed, schema) if errors: return Response( message=f"ERROR: manifest validation failed: {errors}", break_loop=False, ) set_kv(pid, key, parsed) return Response( message=f"{manifest_type!r} manifest saved for project {name!r} (key={key!r}).", break_loop=False, ) if action == "validate": manifest = get_kv(pid, key, default=None) if manifest is None: return Response( message=f"No {manifest_type!r} manifest to validate for project {name!r}.", break_loop=False, ) if not schema: return Response( message=f"No schema defined for {manifest_type!r}; cannot validate.", break_loop=False, ) errors = _validate(manifest, schema) if errors: return Response( message=f"Validation FAILED: {errors}", break_loop=False, ) return Response( message=f"Validation PASSED for {manifest_type!r} manifest of project {name!r}.", break_loop=False, ) return Response( message=f"Unknown action {action!r}. Supported: create, read, update, list, validate", break_loop=False, )