94 lines
3.3 KiB
Python
94 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import asdict, dataclass
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TaskPacket:
|
|
objective: str
|
|
scope: str
|
|
repo: str
|
|
branch_policy: str
|
|
acceptance_tests: tuple[str, ...]
|
|
commit_policy: str
|
|
reporting_contract: str
|
|
escalation_policy: str
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
payload = asdict(self)
|
|
payload['acceptance_tests'] = list(self.acceptance_tests)
|
|
return payload
|
|
|
|
@classmethod
|
|
def from_dict(cls, payload: dict[str, Any]) -> 'TaskPacket':
|
|
acceptance_tests = payload.get('acceptance_tests', ())
|
|
if not isinstance(acceptance_tests, list):
|
|
raise TaskPacketValidationError(['acceptance_tests must be a list'])
|
|
packet = cls(
|
|
objective=str(payload.get('objective', '')),
|
|
scope=str(payload.get('scope', '')),
|
|
repo=str(payload.get('repo', '')),
|
|
branch_policy=str(payload.get('branch_policy', '')),
|
|
acceptance_tests=tuple(str(test) for test in acceptance_tests),
|
|
commit_policy=str(payload.get('commit_policy', '')),
|
|
reporting_contract=str(payload.get('reporting_contract', '')),
|
|
escalation_policy=str(payload.get('escalation_policy', '')),
|
|
)
|
|
validate_task_packet(packet)
|
|
return packet
|
|
|
|
|
|
class TaskPacketValidationError(ValueError):
|
|
def __init__(self, errors: list[str]):
|
|
self.errors = errors
|
|
super().__init__('; '.join(errors))
|
|
|
|
|
|
def validate_task_packet(packet: TaskPacket) -> None:
|
|
errors: list[str] = []
|
|
for field_name in (
|
|
'objective',
|
|
'scope',
|
|
'repo',
|
|
'branch_policy',
|
|
'commit_policy',
|
|
'reporting_contract',
|
|
'escalation_policy',
|
|
):
|
|
if not getattr(packet, field_name).strip():
|
|
errors.append(f'{field_name} must not be empty')
|
|
for index, test in enumerate(packet.acceptance_tests):
|
|
if not test.strip():
|
|
errors.append(f'acceptance_tests contains an empty value at index {index}')
|
|
if errors:
|
|
raise TaskPacketValidationError(errors)
|
|
|
|
|
|
def load_task_packet(path: str | Path) -> TaskPacket:
|
|
packet_path = Path(path)
|
|
payload = json.loads(packet_path.read_text(encoding='utf-8'))
|
|
if not isinstance(payload, dict):
|
|
raise TaskPacketValidationError(['task packet must be a JSON object'])
|
|
return TaskPacket.from_dict(payload)
|
|
|
|
|
|
def render_worker_prompt(packet: TaskPacket) -> str:
|
|
lines = [
|
|
'You are operating as a local worker node in a managed coding harness.',
|
|
f'Objective: {packet.objective}',
|
|
f'Scope: {packet.scope}',
|
|
f'Repository: {packet.repo}',
|
|
f'Branch policy: {packet.branch_policy}',
|
|
f'Commit policy: {packet.commit_policy}',
|
|
f'Reporting contract: {packet.reporting_contract}',
|
|
f'Escalation policy: {packet.escalation_policy}',
|
|
]
|
|
if packet.acceptance_tests:
|
|
lines.append('Acceptance tests the manager will run after you finish:')
|
|
lines.extend(f'- {command}' for command in packet.acceptance_tests)
|
|
lines.append('Make the required code or documentation changes in the workspace and return a concise final answer describing what changed and any blockers.')
|
|
return '\n'.join(lines)
|