5769c1cd22
- Auto-Registration-Bug behoben (register_project/get_project_id/resolve_project Trennung) - 25 Tests gruen (Pytest) - block_compactor-Tool refactored (Option B: Soft-Check statt Hard-Block) - 4 Restbaustellen gefixt - DB-Schema: plugin_settings-Tabelle hinzugefuegt - 3 Schattenprojekte aus DB geloescht - Plan v3 + Refactor-Plan + Worklog dokumentiert
159 lines
5.7 KiB
Python
159 lines
5.7 KiB
Python
"""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,
|
||
)
|