"""Deterministic orchestration helpers for v0.20.""" from __future__ import annotations import json from datetime import datetime, timezone from pathlib import Path from scripts.runtime.artifacts import ensure_phase_dirs, load_manifest, write_manifest from scripts.runtime.methods import ResearchMethodRegistry from scripts.runtime.tasks import generate_task_cards, generate_task_cards_from_research_brief, write_task_cards def utc_now_iso() -> str: return datetime.now(timezone.utc).replace(microsecond=0).isoformat() def create_phase2_task_cards( project_root: Path, *, axes: list[str] | None = None, dry_run: bool = False, ) -> list[dict[str, object]]: framework = project_root / "phase1" / "framework.md" if not framework.exists(): raise FileNotFoundError(f"framework not found: {framework}") method_key = None if (project_root / "manifest.json").exists(): method_key = load_manifest(project_root).get("research_method") method = ResearchMethodRegistry().get(method_key) research_brief_path = project_root / "phase1" / "research_brief.json" framework_text = framework.read_text(encoding="utf-8") if research_brief_path.exists(): research_brief = json.loads(research_brief_path.read_text(encoding="utf-8")) cards = generate_task_cards_from_research_brief( project_root.name, framework_text, research_brief, axes=axes, method=method, ) else: cards = generate_task_cards(project_root.name, framework_text, axes=axes, method=method) if not dry_run: ensure_phase_dirs(project_root) write_task_cards(project_root / "phase2" / "task_cards.json", cards) manifest = load_manifest(project_root) phase2 = manifest.setdefault("phase2", {}) phase2.update( { "status": "in_progress", "runtime": "python-core-v0.20", "research_method": method.key, "task_cards_path": "phase2/task_cards.json", "task_cards_total": len(cards), "updated_at": utc_now_iso(), } ) write_manifest(project_root, manifest) return [card.to_dict() for card in cards] def write_placeholder_packets( project_root: Path, task_cards: list[dict[str, object]], *, dry_run: bool = False, ) -> int: """Create packet skeletons for manual/API completion. This keeps the first v0.20 implementation deterministic and resumable; LLM calls can later fill the same schema without changing downstream readers. """ count = 0 for card in task_cards: packet_path = project_root / str(card["output_packet"]) packet = { "task_id": card["task_id"], "claims": [], "evidence_items": [], "counter_evidence": [], "source_ids": [], "source_quality_notes": [], "open_questions": ["待由 Python role worker 调用模型补全。"], "raw_quotes_or_notes": [], } if not dry_run: packet_path.parent.mkdir(parents=True, exist_ok=True) packet_path.write_text(json.dumps(packet, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") count += 1 return count