255 lines
7.1 KiB
Python
255 lines
7.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
artifact_define.py - Define artifact metadata for Betty Framework skills
|
|
|
|
Helps create artifact_metadata blocks that declare what artifacts a skill
|
|
produces and consumes, enabling interoperability.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import yaml
|
|
from typing import Dict, Any, List, Optional
|
|
from pathlib import Path
|
|
|
|
|
|
from betty.config import BASE_DIR
|
|
from betty.logging_utils import setup_logger
|
|
|
|
logger = setup_logger(__name__)
|
|
|
|
# Known artifact types - loaded from registry/artifact_types.json
|
|
# To update the registry, modify registry/artifact_types.json and reload
|
|
from skills.artifact.define.registry_loader import (
|
|
KNOWN_ARTIFACT_TYPES,
|
|
load_artifact_registry,
|
|
reload_registry,
|
|
get_artifact_count,
|
|
is_registered,
|
|
get_artifact_metadata
|
|
)
|
|
|
|
# For backward compatibility, expose the registry as module-level variable
|
|
# Note: The registry is now data-driven and loaded from JSON
|
|
# Do not modify this file to add new artifact types
|
|
# Instead, update registry/artifact_types.json
|
|
|
|
|
|
|
|
|
|
|
|
def get_artifact_definition(artifact_type: str) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Get the definition for a known artifact type.
|
|
|
|
Args:
|
|
artifact_type: Artifact type identifier
|
|
|
|
Returns:
|
|
Artifact definition dictionary with schema, file_pattern, etc., or None if unknown
|
|
"""
|
|
if artifact_type in KNOWN_ARTIFACT_TYPES:
|
|
definition = {"type": artifact_type}
|
|
definition.update(KNOWN_ARTIFACT_TYPES[artifact_type])
|
|
return definition
|
|
return None
|
|
|
|
|
|
def validate_artifact_type(artifact_type: str) -> tuple[bool, Optional[str]]:
|
|
"""
|
|
Validate that an artifact type is known or suggest registering it.
|
|
|
|
Args:
|
|
artifact_type: Artifact type identifier
|
|
|
|
Returns:
|
|
Tuple of (is_valid, warning_message)
|
|
"""
|
|
if artifact_type in KNOWN_ARTIFACT_TYPES:
|
|
return True, None
|
|
|
|
warning = f"Artifact type '{artifact_type}' is not in the known registry. "
|
|
warning += "Consider documenting it in docs/ARTIFACT_STANDARDS.md and creating a schema."
|
|
return False, warning
|
|
|
|
|
|
def generate_artifact_metadata(
|
|
skill_name: str,
|
|
produces: Optional[List[str]] = None,
|
|
consumes: Optional[List[str]] = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Generate artifact metadata structure.
|
|
|
|
Args:
|
|
skill_name: Name of the skill
|
|
produces: List of artifact types produced
|
|
consumes: List of artifact types consumed
|
|
|
|
Returns:
|
|
Artifact metadata dictionary
|
|
"""
|
|
metadata = {}
|
|
warnings = []
|
|
|
|
# Build produces section
|
|
if produces:
|
|
produces_list = []
|
|
for artifact_type in produces:
|
|
is_known, warning = validate_artifact_type(artifact_type)
|
|
if warning:
|
|
warnings.append(warning)
|
|
|
|
artifact_def = {"type": artifact_type}
|
|
|
|
# Add known metadata if available
|
|
if artifact_type in KNOWN_ARTIFACT_TYPES:
|
|
known = KNOWN_ARTIFACT_TYPES[artifact_type]
|
|
if "schema" in known:
|
|
artifact_def["schema"] = known["schema"]
|
|
if "file_pattern" in known:
|
|
artifact_def["file_pattern"] = known["file_pattern"]
|
|
if "content_type" in known:
|
|
artifact_def["content_type"] = known["content_type"]
|
|
if "description" in known:
|
|
artifact_def["description"] = known["description"]
|
|
|
|
produces_list.append(artifact_def)
|
|
|
|
metadata["produces"] = produces_list
|
|
|
|
# Build consumes section
|
|
if consumes:
|
|
consumes_list = []
|
|
for artifact_type in consumes:
|
|
is_known, warning = validate_artifact_type(artifact_type)
|
|
if warning:
|
|
warnings.append(warning)
|
|
|
|
artifact_def = {
|
|
"type": artifact_type,
|
|
"required": True # Default to required
|
|
}
|
|
|
|
# Add description if known
|
|
if artifact_type in KNOWN_ARTIFACT_TYPES:
|
|
known = KNOWN_ARTIFACT_TYPES[artifact_type]
|
|
if "description" in known:
|
|
artifact_def["description"] = known["description"]
|
|
|
|
consumes_list.append(artifact_def)
|
|
|
|
metadata["consumes"] = consumes_list
|
|
|
|
return metadata, warnings
|
|
|
|
|
|
def format_as_yaml(metadata: Dict[str, Any]) -> str:
|
|
"""
|
|
Format artifact metadata as YAML for inclusion in skill.yaml.
|
|
|
|
Args:
|
|
metadata: Artifact metadata dictionary
|
|
|
|
Returns:
|
|
Formatted YAML string
|
|
"""
|
|
yaml_str = "artifact_metadata:\n"
|
|
yaml_str += yaml.dump(metadata, default_flow_style=False, indent=2, sort_keys=False)
|
|
return yaml_str
|
|
|
|
|
|
def main():
|
|
"""CLI entry point."""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Define artifact metadata for Betty Framework skills"
|
|
)
|
|
parser.add_argument(
|
|
"skill_name",
|
|
help="Name of the skill (e.g., api.define)"
|
|
)
|
|
parser.add_argument(
|
|
"--produces",
|
|
nargs="+",
|
|
help="Artifact types this skill produces"
|
|
)
|
|
parser.add_argument(
|
|
"--consumes",
|
|
nargs="+",
|
|
help="Artifact types this skill consumes"
|
|
)
|
|
parser.add_argument(
|
|
"--output-file",
|
|
default="artifact_metadata.yaml",
|
|
help="Output file path"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
logger.info(f"Generating artifact metadata for skill: {args.skill_name}")
|
|
|
|
try:
|
|
# Generate metadata
|
|
metadata, warnings = generate_artifact_metadata(
|
|
args.skill_name,
|
|
produces=args.produces,
|
|
consumes=args.consumes
|
|
)
|
|
|
|
# Format as YAML
|
|
yaml_content = format_as_yaml(metadata)
|
|
|
|
# Save to file
|
|
output_path = args.output_file
|
|
with open(output_path, 'w') as f:
|
|
f.write(yaml_content)
|
|
|
|
logger.info(f"✅ Generated artifact metadata: {output_path}")
|
|
|
|
# Print to stdout
|
|
print("\n# Add this to your skill.yaml:\n")
|
|
print(yaml_content)
|
|
|
|
# Show warnings
|
|
if warnings:
|
|
logger.warning("\n⚠️ Warnings:")
|
|
for warning in warnings:
|
|
logger.warning(f" - {warning}")
|
|
|
|
# Print summary
|
|
logger.info("\n📋 Summary:")
|
|
if metadata.get("produces"):
|
|
logger.info(f" Produces: {', '.join(a['type'] for a in metadata['produces'])}")
|
|
if metadata.get("consumes"):
|
|
logger.info(f" Consumes: {', '.join(a['type'] for a in metadata['consumes'])}")
|
|
|
|
# Success result
|
|
result = {
|
|
"ok": True,
|
|
"status": "success",
|
|
"skill_name": args.skill_name,
|
|
"metadata": metadata,
|
|
"output_file": output_path,
|
|
"warnings": warnings
|
|
}
|
|
|
|
print("\n" + json.dumps(result, indent=2))
|
|
sys.exit(0)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to generate artifact metadata: {e}")
|
|
result = {
|
|
"ok": False,
|
|
"status": "failed",
|
|
"error": str(e)
|
|
}
|
|
print(json.dumps(result, indent=2))
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|