Files
magnus919_agent-skills/eval_runner/manifest.py
T
usernameandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 476d7e11b0 feat(ci): add linting, formatting, coverage, and security configs
Add ruff linter/formatter with pre-commit hooks, pytest-cov with 60%
coverage threshold, CODEOWNERS, Dependabot for pip/GHA updates, and
.env.example. Auto-fix existing ruff violations across eval_runner/
and scripts/.

10 agent-readiness criteria resolved: lint_config, formatter,
pre_commit_hooks, naming_consistency, dead_code_detection,
test_coverage_thresholds, test_performance_tracking, codeowners,
dependency_update_automation, env_template.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-07-29 17:43:41 -04:00

118 lines
3.7 KiB
Python

"""Run manifest construction and serialization."""
from __future__ import annotations
import json
import subprocess
import uuid
from datetime import datetime
from pathlib import Path
from typing import Any
from .models import AdapterInput, AdapterOutput
from .path_safety import contained_path, hash_contained_file, validate_case_id
MANIFEST_SCHEMA_VERSION = 1
def _git_tree_hash(skill_path: Path) -> str:
result = subprocess.run(
["git", "log", "-1", "--format=%T", "--", str(skill_path)],
capture_output=True,
text=True,
cwd=skill_path,
check=False,
)
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip()[:16]
return "unknown"
def _skill_name(skill_path: Path) -> str:
skill_md = skill_path / "SKILL.md"
if skill_md.is_file():
return skill_path.name
return skill_path.name
def build_manifest(
*,
adapter_name: str,
adapter_version: str,
harness_name: str,
harness_version: str,
model_provider: str,
model_id: str,
adapter_input: AdapterInput,
adapter_output: AdapterOutput,
started_at: datetime,
finished_at: datetime,
) -> dict[str, Any]:
case = adapter_input.case
skill_path = adapter_input.skill_path
artifact_digests: dict[str, str] = {}
for artifact_rel in adapter_output.artifacts:
artifact_digests[artifact_rel] = (
hash_contained_file(adapter_input.output_dir, artifact_rel) or "missing"
)
failures: list[dict[str, str]] = []
if adapter_output.error:
failures.append({"type": "execution_error", "message": adapter_output.error})
return {
"schema_version": MANIFEST_SCHEMA_VERSION,
"trial_id": str(uuid.uuid4()),
"candidate": {
"skill_name": _skill_name(skill_path),
"skill_path": skill_path.name,
"tree_hash": _git_tree_hash(skill_path),
},
"case": {
"case_id": case.id,
"prompt_hash": case.prompt_hash,
"fixture_hashes": case.fixture_hashes(skill_path),
},
"adapter": {
"name": adapter_name,
"version": adapter_version,
},
"harness": {
"name": harness_name,
"version": harness_version,
},
"model": {
"provider": model_provider,
"model_id": model_id,
},
"permissions": adapter_input.permissions,
"network_policy": adapter_input.limits.get("network_policy", "unspecified"),
"limits": adapter_input.limits,
"cache_state": adapter_input.harness_config.get("cache_state", "unspecified"),
"started_at": started_at.isoformat(),
"finished_at": finished_at.isoformat(),
"status": adapter_output.exit_status.value,
"outputs": {
"response": adapter_output.response,
"activation_evidence": adapter_output.activation_evidence,
"artifact_digests": artifact_digests,
"tool_event_count": len(adapter_output.tool_events),
},
"duration_ms": adapter_output.duration_ms,
"token_usage": adapter_output.token_usage,
"failures": failures,
"missing_evidence": adapter_output.missing_evidence(),
}
def write_manifest(manifest: dict[str, Any], output_dir: Path) -> Path:
output_dir.mkdir(parents=True, exist_ok=True)
trial_id = manifest.get("trial_id", "unknown")
case_id = manifest.get("case", {}).get("case_id", "unknown")
validate_case_id(case_id)
filename = f"{case_id}--{trial_id[:8]}.manifest.json"
path = contained_path(output_dir, filename)
path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
return path