Files
a0_software_orchestrator/tools/repo_manifest.py
T

159 lines
5.7 KiB
Python
Raw Normal View History

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