Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:26:08 +08:00
commit 8f22ddf339
295 changed files with 59710 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
# artifact.scaffold
Generate new artifact templates automatically from metadata inputs.
## Overview
The `artifact.scaffold` skill creates fully compliant artifact descriptors in one call. It generates valid `.artifact.yaml` files, assigns auto-incremented versions starting at 0.1.0, and registers artifacts in the artifacts registry.
## Features
- **Automatic Generation**: Creates artifact YAML files from metadata inputs
- **Schema Definition**: Supports field definitions with types, descriptions, and required flags
- **Inheritance**: Supports extending from base artifacts
- **Registry Management**: Automatically registers artifacts in `registry/artifacts.json`
- **Validation**: Optional `--validate` flag to validate generated artifacts
- **Version Management**: Auto-assigns version 0.1.0 to new artifacts
## Usage
### Basic Example
```bash
python3 skills/artifact.scaffold/artifact_scaffold.py \
--id "new.artifact" \
--category "report"
```
### With Field Definitions
```bash
python3 skills/artifact.scaffold/artifact_scaffold.py \
--id "new.artifact" \
--category "report" \
--fields '[{"name":"summary","type":"string","description":"Summary field","required":true}]'
```
### With Inheritance and Validation
```bash
python3 skills/artifact.scaffold/artifact_scaffold.py \
--id "new.artifact" \
--category "report" \
--extends "base.artifact" \
--fields '[{"name":"summary","type":"string"}]' \
--validate
```
### Custom Output Path
```bash
python3 skills/artifact.scaffold/artifact_scaffold.py \
--id "new.artifact" \
--category "report" \
--output "custom/path/artifact.yaml"
```
## Input Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `--id` | string | Yes | Unique identifier for the artifact (e.g., "new.artifact") |
| `--category` | string | Yes | Category/type of artifact (e.g., "report", "specification") |
| `--extends` | string | No | Base artifact to extend from |
| `--fields` | JSON array | No | Field definitions with name, type, description, and required properties |
| `--output` | string | No | Custom output path for the artifact file |
| `--validate` | flag | No | Validate the artifact after generation |
## Field Definition Format
Fields are provided as a JSON array with the following structure:
```json
[
{
"name": "field_name",
"type": "string|number|boolean|object|array",
"description": "Field description",
"required": true|false
}
]
```
## Output
The skill outputs a JSON response with the following structure:
```json
{
"ok": true,
"status": "success",
"artifact_id": "new.artifact",
"file_path": "/path/to/artifact.yaml",
"version": "0.1.0",
"category": "report",
"registry_path": "/path/to/registry/artifacts.json",
"artifacts_registered": 1,
"validation": {
"valid": true,
"errors": [],
"warnings": []
}
}
```
## Generated Artifact Structure
The skill generates artifact YAML files with the following structure:
```yaml
id: new.artifact
version: 0.1.0
category: report
created_at: '2025-10-26T00:00:00.000000Z'
metadata:
description: new.artifact artifact
tags:
- report
extends: base.artifact # Optional
schema:
type: object
properties:
summary:
type: string
description: Summary field
required:
- summary
```
## Registry Management
Artifacts are automatically registered in `registry/artifacts.json`:
```json
{
"registry_version": "1.0.0",
"generated_at": "2025-10-26T00:00:00.000000Z",
"artifacts": [
{
"id": "new.artifact",
"version": "0.1.0",
"category": "report",
"created_at": "2025-10-26T00:00:00.000000Z",
"description": "new.artifact artifact",
"tags": ["report"],
"extends": "base.artifact",
"schema": { ... }
}
]
}
```
## Dependencies
- `artifact.define`: For artifact type definitions and validation
## Status
**Active** - Ready for production use
## Tags
- artifacts
- scaffolding
- generation
- templates
- metadata

View File

@@ -0,0 +1 @@
# Auto-generated package initializer for skills.

View File

@@ -0,0 +1,410 @@
#!/usr/bin/env python3
"""
artifact_scaffold.py - Generate new artifact templates automatically from metadata inputs
Creates compliant artifact descriptors, registers them in the registry, and optionally validates them.
"""
import os
import sys
import json
import yaml
import argparse
from typing import Dict, Any, List, Optional
from pathlib import Path
from datetime import datetime
from betty.config import BASE_DIR
from betty.logging_utils import setup_logger
from betty.errors import format_error_response
logger = setup_logger(__name__)
# Default artifact directories
ARTIFACTS_DIR = os.path.join(BASE_DIR, "artifacts")
REGISTRY_DIR = os.path.join(BASE_DIR, "registry")
ARTIFACTS_REGISTRY_FILE = os.path.join(REGISTRY_DIR, "artifacts.json")
def ensure_directories():
"""Ensure required directories exist"""
os.makedirs(ARTIFACTS_DIR, exist_ok=True)
os.makedirs(REGISTRY_DIR, exist_ok=True)
def load_artifacts_registry() -> Dict[str, Any]:
"""Load the artifacts registry, or create a new one if it doesn't exist"""
if os.path.exists(ARTIFACTS_REGISTRY_FILE):
try:
with open(ARTIFACTS_REGISTRY_FILE, 'r') as f:
return json.load(f)
except Exception as e:
logger.warning(f"Failed to load artifacts registry: {e}")
return create_empty_registry()
else:
return create_empty_registry()
def create_empty_registry() -> Dict[str, Any]:
"""Create a new empty artifacts registry"""
return {
"registry_version": "1.0.0",
"generated_at": datetime.utcnow().isoformat() + "Z",
"artifacts": []
}
def save_artifacts_registry(registry: Dict[str, Any]):
"""Save the artifacts registry"""
registry["generated_at"] = datetime.utcnow().isoformat() + "Z"
with open(ARTIFACTS_REGISTRY_FILE, 'w') as f:
json.dump(registry, f, indent=2)
logger.info(f"Saved artifacts registry to {ARTIFACTS_REGISTRY_FILE}")
def generate_artifact_yaml(
artifact_id: str,
category: str,
extends: Optional[str] = None,
fields: Optional[List[Dict[str, str]]] = None,
version: str = "0.1.0"
) -> Dict[str, Any]:
"""
Generate an artifact YAML structure
Args:
artifact_id: Unique identifier for the artifact (e.g., "new.artifact")
category: Category/type of artifact (e.g., "report", "specification")
extends: Optional base artifact to extend from
fields: List of field definitions with name and type
version: Semantic version (default: 0.1.0)
Returns:
Dictionary representing the artifact structure
"""
artifact = {
"id": artifact_id,
"version": version,
"category": category,
"created_at": datetime.utcnow().isoformat() + "Z",
"metadata": {
"description": f"{artifact_id} artifact",
"tags": [category]
}
}
if extends:
artifact["extends"] = extends
if fields:
artifact["schema"] = {
"type": "object",
"properties": {},
"required": []
}
for field in fields:
field_name = field.get("name", "")
field_type = field.get("type", "string")
field_description = field.get("description", f"{field_name} field")
field_required = field.get("required", False)
artifact["schema"]["properties"][field_name] = {
"type": field_type,
"description": field_description
}
if field_required:
artifact["schema"]["required"].append(field_name)
return artifact
def get_artifact_filename(artifact_id: str) -> str:
"""
Generate filename for artifact YAML file
Args:
artifact_id: The artifact ID (e.g., "new.artifact")
Returns:
Filename in format: {artifact_id}.artifact.yaml
"""
# Replace dots with hyphens for filename
safe_id = artifact_id.replace(".", "-")
return f"{safe_id}.artifact.yaml"
def save_artifact_yaml(artifact: Dict[str, Any], output_path: Optional[str] = None) -> str:
"""
Save artifact to YAML file
Args:
artifact: The artifact dictionary
output_path: Optional custom output path
Returns:
Path to the saved file
"""
artifact_id = artifact["id"]
if output_path:
file_path = output_path
else:
filename = get_artifact_filename(artifact_id)
file_path = os.path.join(ARTIFACTS_DIR, filename)
with open(file_path, 'w') as f:
yaml.dump(artifact, f, default_flow_style=False, sort_keys=False)
logger.info(f"Saved artifact to {file_path}")
return file_path
def register_artifact(artifact: Dict[str, Any]) -> Dict[str, Any]:
"""
Register artifact in the artifacts registry
Args:
artifact: The artifact dictionary
Returns:
The updated registry
"""
registry = load_artifacts_registry()
# Check if artifact already exists
artifact_id = artifact["id"]
existing_idx = None
for idx, reg_artifact in enumerate(registry["artifacts"]):
if reg_artifact["id"] == artifact_id:
existing_idx = idx
break
# Create registry entry
registry_entry = {
"id": artifact["id"],
"version": artifact["version"],
"category": artifact["category"],
"created_at": artifact["created_at"],
"description": artifact.get("metadata", {}).get("description", ""),
"tags": artifact.get("metadata", {}).get("tags", [])
}
if "extends" in artifact:
registry_entry["extends"] = artifact["extends"]
if "schema" in artifact:
registry_entry["schema"] = artifact["schema"]
# Update or add entry
if existing_idx is not None:
registry["artifacts"][existing_idx] = registry_entry
logger.info(f"Updated artifact {artifact_id} in registry")
else:
registry["artifacts"].append(registry_entry)
logger.info(f"Added artifact {artifact_id} to registry")
save_artifacts_registry(registry)
return registry
def scaffold_artifact(
artifact_id: str,
category: str,
extends: Optional[str] = None,
fields: Optional[List[Dict[str, str]]] = None,
output_path: Optional[str] = None,
validate: bool = False
) -> Dict[str, Any]:
"""
Main scaffolding function
Args:
artifact_id: Unique identifier for the artifact
category: Category/type of artifact
extends: Optional base artifact to extend from
fields: List of field definitions
output_path: Optional custom output path
validate: Whether to run validation after scaffolding
Returns:
Result dictionary with status and details
"""
try:
ensure_directories()
# Generate artifact structure
artifact = generate_artifact_yaml(
artifact_id=artifact_id,
category=category,
extends=extends,
fields=fields
)
# Save to file
file_path = save_artifact_yaml(artifact, output_path)
# Register in artifacts registry
registry = register_artifact(artifact)
result = {
"ok": True,
"status": "success",
"artifact_id": artifact_id,
"file_path": file_path,
"version": artifact["version"],
"category": category,
"registry_path": ARTIFACTS_REGISTRY_FILE,
"artifacts_registered": len(registry["artifacts"])
}
# Optional validation
if validate:
validation_result = validate_artifact(file_path)
result["validation"] = validation_result
return result
except Exception as e:
logger.error(f"Failed to scaffold artifact: {e}", exc_info=True)
return {
"ok": False,
"status": "failed",
"error": str(e),
"details": format_error_response(e)
}
def validate_artifact(file_path: str) -> Dict[str, Any]:
"""
Validate an artifact YAML file
Args:
file_path: Path to the artifact YAML file
Returns:
Validation result dictionary
"""
try:
with open(file_path, 'r') as f:
artifact = yaml.safe_load(f)
errors = []
warnings = []
# Required fields
required_fields = ["id", "version", "category", "created_at"]
for field in required_fields:
if field not in artifact:
errors.append(f"Missing required field: {field}")
# Version format check
if "version" in artifact:
version = artifact["version"]
parts = version.split(".")
if len(parts) != 3 or not all(p.isdigit() for p in parts):
warnings.append(f"Version {version} may not follow semantic versioning (X.Y.Z)")
# Category check
if "category" in artifact and not artifact["category"]:
warnings.append("Category is empty")
# Schema validation
if "schema" in artifact:
schema = artifact["schema"]
if "properties" not in schema:
warnings.append("Schema missing 'properties' field")
is_valid = len(errors) == 0
return {
"valid": is_valid,
"errors": errors,
"warnings": warnings,
"file_path": file_path
}
except Exception as e:
return {
"valid": False,
"errors": [f"Failed to validate: {str(e)}"],
"warnings": [],
"file_path": file_path
}
def main():
"""Main entry point"""
parser = argparse.ArgumentParser(
description="Generate new artifact templates from metadata"
)
parser.add_argument(
"--id",
required=True,
help="Artifact ID (e.g., 'new.artifact')"
)
parser.add_argument(
"--category",
required=True,
help="Artifact category (e.g., 'report', 'specification')"
)
parser.add_argument(
"--extends",
help="Base artifact to extend from (optional)"
)
parser.add_argument(
"--fields",
help="JSON string of field definitions (e.g., '[{\"name\":\"summary\",\"type\":\"string\"}]')"
)
parser.add_argument(
"--output",
help="Custom output path for the artifact file"
)
parser.add_argument(
"--validate",
action="store_true",
help="Validate the artifact after generation"
)
args = parser.parse_args()
# Parse fields if provided
fields = None
if args.fields:
try:
fields = json.loads(args.fields)
except json.JSONDecodeError as e:
print(json.dumps({
"ok": False,
"status": "failed",
"error": f"Invalid JSON for fields: {e}"
}, indent=2))
sys.exit(1)
# Scaffold the artifact
result = scaffold_artifact(
artifact_id=args.id,
category=args.category,
extends=args.extends,
fields=fields,
output_path=args.output,
validate=args.validate
)
# Output result as JSON
print(json.dumps(result, indent=2))
# Exit with appropriate code
sys.exit(0 if result.get("ok", False) else 1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,136 @@
name: artifact.scaffold
version: 0.1.0
description: >
Generate new artifact templates automatically from metadata inputs.
Creates fully compliant artifact descriptors with auto-incremented versions,
saves them as .artifact.yaml files, and registers them in the artifacts registry.
Supports optional validation of generated artifacts.
inputs:
- name: id
type: string
required: true
description: Unique identifier for the artifact (e.g., "new.artifact")
- name: category
type: string
required: true
description: Category/type of artifact (e.g., "report", "specification")
- name: extends
type: string
required: false
description: Optional base artifact to extend from (e.g., "base.artifact")
- name: fields
type: array
required: false
description: List of field definitions with name, type, description, and required properties
- name: output
type: string
required: false
description: Custom output path for the artifact file (defaults to artifacts/{id}.artifact.yaml)
- name: validate
type: boolean
required: false
default: false
description: Whether to validate the artifact after generation
outputs:
- name: artifact_id
type: string
description: ID of the generated artifact
- name: file_path
type: string
description: Path to the generated artifact YAML file
- name: version
type: string
description: Version assigned to the artifact (default 0.1.0)
- name: category
type: string
description: Category of the artifact
- name: registry_path
type: string
description: Path to the artifacts registry
- name: artifacts_registered
type: integer
description: Total number of artifacts in the registry
- name: validation
type: object
required: false
description: Validation results if --validate flag was used
dependencies:
- artifact.define
entrypoints:
- command: /skill/artifact/scaffold
handler: artifact_scaffold.py
runtime: python
description: >
Generate a new artifact template from metadata. Creates a valid .artifact.yaml
file with the specified structure, registers it in the artifacts registry,
and optionally validates the output.
parameters:
- name: id
type: string
required: true
description: Artifact ID in namespace.name format
- name: category
type: string
required: true
description: Artifact category
- name: extends
type: string
required: false
description: Base artifact to extend
- name: fields
type: array
required: false
description: Field definitions as JSON array
- name: output
type: string
required: false
description: Custom output path
- name: validate
type: boolean
required: false
description: Validate after generation
permissions:
- filesystem:read
- filesystem:write
status: active
tags:
- artifacts
- scaffolding
- generation
- templates
- metadata
artifact_metadata:
produces:
- type: artifact-definition
description: Generated artifact YAML descriptor file
file_pattern: "*.artifact.yaml"
content_type: application/yaml
- type: artifact-registry
description: Updated artifacts registry with new entries
file_pattern: "registry/artifacts.json"
content_type: application/json
consumes:
- type: artifact-metadata
description: Optional artifact metadata for extension
file_pattern: "*.artifact.yaml"
content_type: application/yaml