49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
|
|
"""Task specification the worker consumes.
|
||
|
|
|
||
|
|
MVP source is a JSON file matching what the activity-core issue-core sink
|
||
|
|
emits per rule action (task_template/description/target_repo/labels), plus
|
||
|
|
executor-specific fields. A future source polls issue-core directly.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
from dataclasses import dataclass, field
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
|
||
|
|
class TaskSpecError(ValueError):
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class TaskSpec:
|
||
|
|
title: str
|
||
|
|
description: str
|
||
|
|
target_repo: Path
|
||
|
|
agent: str = "coach"
|
||
|
|
labels: list[str] = field(default_factory=list)
|
||
|
|
hub_task_id: str | None = None
|
||
|
|
completion_event_type: str = "executor_run"
|
||
|
|
timeout_seconds: int = 900
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def from_file(cls, path: str | Path) -> "TaskSpec":
|
||
|
|
raw = json.loads(Path(path).read_text())
|
||
|
|
missing = {"title", "description", "target_repo"} - set(raw)
|
||
|
|
if missing:
|
||
|
|
raise TaskSpecError(f"task spec missing field(s): {', '.join(sorted(missing))}")
|
||
|
|
target = Path(raw["target_repo"]).expanduser()
|
||
|
|
if not (target / ".git").is_dir():
|
||
|
|
raise TaskSpecError(f"target_repo is not a git repository: {target}")
|
||
|
|
return cls(
|
||
|
|
title=str(raw["title"]),
|
||
|
|
description=str(raw["description"]),
|
||
|
|
target_repo=target,
|
||
|
|
agent=str(raw.get("agent", "coach")),
|
||
|
|
labels=[str(label) for label in raw.get("labels", [])],
|
||
|
|
hub_task_id=raw.get("hub_task_id"),
|
||
|
|
completion_event_type=str(raw.get("completion_event_type", "executor_run")),
|
||
|
|
timeout_seconds=int(raw.get("timeout_seconds", 900)),
|
||
|
|
)
|