Files

1399 lines
63 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Minimal orchestration for README-first reproduction scaffolding."""
from __future__ import annotations
import argparse
import json
import math
import os
import re
import shlex
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any, Dict, List, Optional
from annotate_readme import write_annotated_readme
SKILL_ROOT = Path(__file__).resolve().parents[1]
SOURCE_SHARED_SCRIPTS = Path(__file__).resolve().parents[3] / "shared" / "scripts"
BUNDLED_SHARED_SCRIPTS = SKILL_ROOT / "_bundled" / "shared" / "scripts"
SHARED_SCRIPTS = (
SOURCE_SHARED_SCRIPTS
if (SOURCE_SHARED_SCRIPTS / "command_utils.py").is_file()
else BUNDLED_SHARED_SCRIPTS
)
if str(SHARED_SCRIPTS) not in sys.path:
sys.path.insert(0, str(SHARED_SCRIPTS))
from runtime_runner import run_persistent_command
from model_adapter import ModelAdapterError, load_model_profile, missing_capabilities
def load_lessons_store():
"""Load the shared lesson store; return None when unavailable (optional feature)."""
import importlib.util
module_path = SHARED_SCRIPTS / "lessons_store.py"
if not module_path.exists():
return None
spec = importlib.util.spec_from_file_location("lessons_store", module_path)
if spec is None or spec.loader is None:
return None
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module)
except Exception:
return None
return module
def maybe_record_lesson(repo_path: Path, context: Dict[str, Any]) -> Optional[str]:
"""Record failure blockers and later resolutions per the continuous-learning policy."""
store = load_lessons_store()
if store is None or not store.lessons_enabled():
return None
fingerprint = store.repo_fingerprint(repo_path)
status = context.get("status")
try:
if status in {"partial", "blocked"}:
path = store.record_lesson(
kind="failure-fix",
skill="ai-research-reproduction",
summary=f"[{status}] {context.get('main_blocker', 'unrecorded blocker')}",
detail=str(context.get("documented_command") or ""),
fingerprint=fingerprint,
)
return str(path) if path else None
if status == "success":
prior_failures = [
item
for item in store.load_lessons()
if item.get("fingerprint") == fingerprint and str(item.get("summary", "")).startswith("[")
]
if prior_failures:
path = store.record_lesson(
kind="failure-fix",
skill="ai-research-reproduction",
summary=f"[resolved] {context.get('documented_command')} now succeeds",
detail=f"previous blocker: {prior_failures[-1].get('summary', '')}",
fingerprint=fingerprint,
)
return str(path) if path else None
except Exception:
return None
return None
def locale(user_language: str) -> str:
return "zh" if user_language.lower().startswith("zh") else "en"
def text(user_language: str, en: str, zh: str) -> str:
return zh if locale(user_language) == "zh" else en
def run_json(script: Path, args: List[str]) -> Dict[str, Any]:
command = [sys.executable, str(script), *args]
child_env = os.environ.copy()
child_env["PYTHONIOENCODING"] = "utf-8"
result = subprocess.run(
command,
check=True,
capture_output=True,
text=True,
encoding="utf-8",
env=child_env,
)
return json.loads(result.stdout)
def write_bundle(script: Path, output_dir: Path, context: Dict[str, Any]) -> None:
output_dir.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as handle:
context_path = Path(handle.name)
handle.write(json.dumps(context, indent=2, ensure_ascii=False))
try:
subprocess.run(
[
sys.executable,
str(script),
"--context-json",
str(context_path),
"--output-dir",
str(output_dir),
],
check=True,
)
finally:
if context_path.exists():
context_path.unlink()
def build_asset_commands(asset_data: Dict[str, Any], user_language: str = "en") -> List[Dict[str, str]]:
"""Report observations, not mandatory preparation invented from directory names."""
commands: List[Dict[str, str]] = []
for item in asset_data.get("manifest", []):
group = item.get("asset_group", "asset")
if item.get("status") == "present":
commands.append({"label": "inferred", "execution_status": "not_run", "command": text(
user_language,
f"# Observed {group} path: {item.get('source_hint')}; contents and applicability are unverified.",
f"# 已发现 {group} 路径:{item.get('source_hint')};内容及是否适用于当前目标尚未验证。",
)})
for hint in asset_data.get("text_hints", []):
descriptor = hint.get("paths") or hint.get("urls") or hint.get("line", "")
source = Path(hint.get("source", "README.md")).name
commands.append({"label": "documented", "execution_status": "not_run", "command": text(
user_language,
f"# Asset hint from {source}: {descriptor}; confirm relevance to the selected command before preparation.",
f"# 来自 {source} 的资源线索:{descriptor};准备前先确认是否与选定命令相关。",
)})
return commands
def derive_dataset_hint(asset_data: Dict[str, Any]) -> str:
for hint in asset_data.get("text_hints", []):
if "dataset" in hint.get("line", "").lower():
return hint.get("paths") or hint.get("urls") or "README-documented dataset"
for item in asset_data.get("manifest", []):
if item.get("asset_group") in {"datasets", "data"} and item.get("status") == "present":
return item.get("source_hint", "repo-local dataset")
return "unknown"
def derive_checkpoint_hint(asset_data: Dict[str, Any]) -> str:
for hint in asset_data.get("text_hints", []):
line = hint.get("line", "").lower()
if "checkpoint" in line or "weight" in line or "model" in line:
return hint.get("paths") or hint.get("urls") or "README-documented checkpoint"
for item in asset_data.get("manifest", []):
if item.get("asset_group") in {"checkpoints", "weights"} and item.get("status") == "present":
return item.get("source_hint", "repo-local checkpoint")
return "none"
def extract_config_path(command: str) -> str | None:
tokens = shlex.split(command, posix=True)
for index, token in enumerate(tokens):
if token in {"--config", "--cfg"} and index + 1 < len(tokens):
return tokens[index + 1]
if token.startswith("--config="):
return token.split("=", 1)[1]
if token.startswith("--cfg="):
return token.split("=", 1)[1]
return None
def estimate_training_duration(repo_path: Path, command: str, max_train_steps: int) -> str:
if max_train_steps > 0:
if max_train_steps <= 200:
return f"roughly minutes to under 1 hour for about {max_train_steps} steps, depending on dataset size and GPU throughput"
if max_train_steps <= 5000:
return f"roughly hours for about {max_train_steps} steps, depending on dataset size and GPU throughput"
return f"likely many hours to multi-day for about {max_train_steps} steps, depending on dataset size and GPU throughput"
config_rel = extract_config_path(command)
if config_rel:
config_path = (repo_path / config_rel).resolve()
if config_path.exists() and config_path.suffix.lower() in {".yaml", ".yml", ".json", ".toml", ".py"}:
text_content = config_path.read_text(encoding="utf-8", errors="replace")
step_match = None
for key in ["max_steps", "total_steps", "train_steps", "num_steps"]:
step_match = re.search(rf"{key}\s*[:=]\s*(\d+)", text_content, flags=re.IGNORECASE)
if step_match:
steps = int(step_match.group(1))
if steps <= 200:
return f"roughly minutes to under 1 hour from config-bound {steps} steps, depending on GPU throughput"
if steps <= 5000:
return f"roughly hours from config-bound {steps} steps, depending on GPU throughput"
return f"likely many hours to multi-day from config-bound {steps} steps, depending on dataset size and GPU throughput"
epoch_match = None
for key in ["epochs", "max_epochs", "num_epochs", "train_epochs"]:
epoch_match = re.search(rf"{key}\s*[:=]\s*(\d+)", text_content, flags=re.IGNORECASE)
if epoch_match:
epochs = int(epoch_match.group(1))
if epochs <= 3:
return f"roughly minutes to under 1 hour for about {epochs} epochs, depending on dataset size and GPU throughput"
if epochs <= 20:
return f"roughly hours for about {epochs} epochs, depending on dataset size and GPU throughput"
return f"likely many hours to multi-day for about {epochs} epochs, depending on dataset size and GPU throughput"
return "unknown; likely hours to multi-day on the full dataset until a bounded schedule is confirmed"
OUT_DIR_RE = re.compile(r"--out[_-]?dir[= ]([\w./-]+)")
def command_score(command: Dict[str, Any], produced_out_dirs: frozenset = frozenset()) -> int:
text_value = str(command.get("command", "")).lower()
kind = command.get("kind", "run")
score = {"run": 40, "smoke": 30, "asset": 10, "setup": 0}.get(kind, 0)
if any(token in text_value for token in ["python ", "python3 ", "./", "whisper "]):
score += 8
if any(token in text_value for token in ["txt2img", "img2img", "amg.py", "transcribe", "infer", "eval"]):
score += 8
# Self-contained commands (pretrained init, downloadable weights) beat
# commands that consume another documented command's training output.
if re.search(r"--init_from=|--pretrained|https?://", text_value):
score += 6
out_match = OUT_DIR_RE.search(text_value)
if out_match and out_match.group(1) in produced_out_dirs and command.get("category") != "training":
score -= 8
if "<" in text_value and ">" in text_value:
score -= 10
if text_value.startswith(("pip install", "conda install", "conda env create", "conda activate", "git clone", "cd ")):
score -= 12
if command.get("category") == "training":
if "--device=cpu" in text_value:
score += 6
if re.search(r"--(?:max_iters|max-steps|max_steps|epochs?)[= ]\d+|--dry-run\b", text_value):
score += 6
if text_value.startswith(("torchrun ", "deepspeed ")) or "--nproc_per_node" in text_value:
score -= 12
return score
LARGE_REMOTE_MODEL_RE = re.compile(
r"--init_from=(?:gpt2-(?:medium|large|xl)|[^\s]*(?:large|xl))\b",
re.IGNORECASE,
)
def command_feasibility(command: Dict[str, Any], repo_path: Optional[Path]) -> tuple[bool, str]:
text_value = str(command.get("command", ""))
if command.get("category") != "inference":
return True, "no static prerequisite blocker detected"
out_match = OUT_DIR_RE.search(text_value)
if out_match and repo_path is not None and not (repo_path / out_match.group(1)).exists():
return False, f"required local output directory is absent: {out_match.group(1)}"
if LARGE_REMOTE_MODEL_RE.search(text_value):
return False, "command implies a large remote pretrained-model download"
return True, "no static prerequisite blocker detected"
def choose_goal(commands: List[Dict[str, Any]], repo_path: Optional[Path] = None) -> Dict[str, Any]:
produced_out_dirs = frozenset(
match.group(1)
for item in commands
if item.get("category") == "training"
for match in [OUT_DIR_RE.search(str(item.get("command", "")).lower())]
if match
)
ranked = sorted(commands, key=lambda item: -command_score(item, produced_out_dirs))
goal_candidates = [
{
"command": item.get("command", ""),
"category": item.get("category"),
"score": command_score(item, produced_out_dirs),
"needs_substitution": bool(item.get("needs_substitution")),
"feasible": command_feasibility(item, repo_path)[0],
"feasibility_reason": command_feasibility(item, repo_path)[1],
}
for item in ranked[:3]
]
for category in ["inference", "evaluation", "training", "other"]:
candidates = [item for item in commands if item.get("category") == category]
if not candidates:
continue
runnable = [
item
for item in candidates
if not item.get("needs_substitution") and command_feasibility(item, repo_path)[0]
]
if not runnable:
continue
best = max(runnable, key=lambda item: command_score(item, produced_out_dirs))
return {
"selected_goal": category,
"goal_priority": category,
"documented_command": best.get("command", ""),
"command_source": best.get("source", "readme"),
"documented_command_kind": best.get("kind", "run"),
"documented_command_section": best.get("section"),
"documented_command_source_file": best.get("source_file"),
"requires_substitution": bool(best.get("needs_substitution")),
"goal_candidates": goal_candidates,
}
return {
"selected_goal": "repo-intake-only",
"goal_priority": "other",
"documented_command": "",
"command_source": "none",
"documented_command_kind": "none",
"documented_command_section": None,
"documented_command_source_file": None,
"requires_substitution": False,
"goal_candidates": goal_candidates,
}
DOC_LINK_RE = re.compile(r"\]\(([^)#\s]+\.md)\)")
DOC_PRIORITY_TOKENS = ("get_started", "getting_started", "install", "quick", "usage", "user_guide", "docs/")
def delegate_to_docs(readme_path: str, extract_script: Path, command_data: Dict[str, Any]) -> Dict[str, Any]:
"""When the README itself yields no runnable command, follow its local doc links.
Real repos (e.g. mmsegmentation) keep all commands in docs/get_started
files; without this the run degrades to repo-intake-only.
"""
if any(item.get("kind") in {"run", "smoke"} for item in command_data.get("commands", [])):
return command_data
readme_file = Path(readme_path)
readme_text = readme_file.read_text(encoding="utf-8-sig", errors="replace")
links: List[tuple] = []
for match in DOC_LINK_RE.finditer(readme_text):
rel = match.group(1)
if rel.startswith(("http://", "https://")):
continue
target = (readme_file.parent / rel).resolve()
if target.exists() and target.suffix.lower() == ".md":
links.append((rel, target))
links.sort(key=lambda item: (0 if any(token in item[0].lower() for token in DOC_PRIORITY_TOKENS) else 1, len(item[0])))
for rel, target in links[:3]:
doc_data = run_json(extract_script, ["--readme", str(target), "--json"])
doc_commands = doc_data.get("commands", [])
for item in doc_commands:
item["source_file"] = rel
command_data["commands"].extend(doc_commands)
if any(item.get("kind") in {"run", "smoke"} for item in doc_commands):
command_data.setdefault("warnings", []).append(
f"README had no runnable commands; delegated extraction to linked doc `{rel}`."
)
break
return command_data
def plan_skill_chain(selected_goal: str, include_analysis_pass: bool, include_paper_gap: bool) -> List[str]:
chain = [
"repo-intake-and-plan",
"env-and-assets-bootstrap",
]
if include_analysis_pass:
chain.append("analyze-project")
chain.append("run-train" if selected_goal == "training" else "minimal-run-and-audit")
if include_paper_gap:
chain.append("paper-context-resolver")
return chain
METRIC_RE = re.compile(
r"\b([A-Za-z][A-Za-z0-9_.-]{1,31})\s*[:=]\s*(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)"
)
METRIC_NOISE_TOKENS = {"loss", "lr", "time", "mem", "epoch", "step", "iter", "iteration"}
def parse_observed_metrics(output_text: str) -> Dict[str, Any]:
observed: Dict[str, float] = {}
for match in METRIC_RE.finditer(output_text):
observed[match.group(1)] = float(match.group(2))
priority = [name for name in observed if not any(token in name.lower() for token in METRIC_NOISE_TOKENS)]
chosen = priority[-1] if priority else (list(observed)[-1] if observed else None)
return {
"observed_metrics": observed,
"best_metric": {"name": chosen, "value": observed[chosen]} if chosen else None,
}
def parse_expected_metrics(values: List[str]) -> Dict[str, float]:
expected: Dict[str, float] = {}
for raw in values:
name, separator, value_text = raw.partition("=")
name = name.strip()
if not separator or not name:
raise ValueError(f"Expected metric must use NAME=VALUE syntax: {raw!r}")
try:
value = float(value_text.strip())
except ValueError as exc:
raise ValueError(f"Expected metric value is not numeric: {raw!r}") from exc
if not math.isfinite(value):
raise ValueError(f"Expected metric value must be finite: {raw!r}")
expected[name] = value
return expected
def compare_expected_metrics(
observed: Dict[str, Any],
expected: Dict[str, float],
absolute_tolerance: float,
) -> Dict[str, Any]:
if not expected:
return {
"status": "not_evaluated",
"reason": "No explicit expected metrics were supplied.",
"absolute_tolerance": absolute_tolerance,
"comparisons": [],
}
observed_by_key = {str(name).lower(): (str(name), value) for name, value in observed.items()}
comparisons: List[Dict[str, Any]] = []
for expected_name, expected_value in expected.items():
matched_observed = observed_by_key.get(expected_name.lower())
if matched_observed is None:
comparisons.append(
{
"metric": expected_name,
"expected": expected_value,
"observed": None,
"absolute_error": None,
"within_tolerance": False,
"reason": "metric_not_observed",
}
)
continue
observed_name, raw_observed_value = matched_observed
try:
observed_value = float(raw_observed_value)
except (TypeError, ValueError):
comparisons.append(
{
"metric": expected_name,
"observed_name": observed_name,
"expected": expected_value,
"observed": raw_observed_value,
"absolute_error": None,
"within_tolerance": False,
"reason": "observed_value_not_numeric",
}
)
continue
absolute_error = abs(observed_value - expected_value)
comparisons.append(
{
"metric": expected_name,
"observed_name": observed_name,
"expected": expected_value,
"observed": observed_value,
"absolute_error": absolute_error,
"within_tolerance": absolute_error <= absolute_tolerance,
}
)
matched = bool(comparisons) and all(item["within_tolerance"] for item in comparisons)
return {
"status": "matched" if matched else "mismatched",
"reason": "All expected metrics are within tolerance." if matched else "At least one expected metric is missing or outside tolerance.",
"absolute_tolerance": absolute_tolerance,
"comparisons": comparisons,
}
def maybe_run_command(
repo_path: Path,
command: str,
timeout: int,
user_language: str,
shell_mode: str = "direct",
runtime_root: Optional[Path] = None,
model_adapter: Optional[Dict[str, Any]] = None,
monitor_gpu: bool = False,
) -> Dict[str, Any]:
if not command:
return {
"status": "not_run",
"documented_command_status": "not_run",
"execution_log": [],
"main_blocker": text(
user_language,
"No documented command was extracted from README.",
"README 中未提取到已文档化命令。",
),
}
selected_runtime_root = (runtime_root or (repo_path / "repro_outputs" / "_runtime")).resolve()
result = run_persistent_command(
repo=repo_path,
command=command,
timeout=timeout,
runtime_root=selected_runtime_root,
shell_mode=shell_mode,
model_adapter=model_adapter,
monitor_gpu=monitor_gpu,
)
if result.get("launch_error"):
exc = result["launch_error"]
return {
"status": "blocked",
"documented_command_status": "blocked",
"execution_log": [f"Command failed before launch: {exc}"],
"main_blocker": text(
user_language,
f"Executable not found for documented command: {exc}",
f"文档命令缺少可执行程序:{exc}",
),
"execution_mode": shell_mode,
**runtime_metadata(result),
}
if result.get("cancelled"):
return {
"status": "partial",
"documented_command_status": "partial",
"execution_log": ["Command cancelled through the runtime control file."],
"main_blocker": text(
user_language,
"The selected documented command was cancelled.",
"选定的文档命令已取消。",
),
"execution_mode": shell_mode,
**runtime_metadata(result),
}
if result.get("timed_out"):
return {
"status": "partial",
"documented_command_status": "partial",
"execution_log": [
item for item in [
f"STDOUT:\n{result.get('stdout', '').strip()}" if result.get("stdout", "").strip() else "",
f"STDERR:\n{result.get('stderr', '').strip()}" if result.get("stderr", "").strip() else "",
f"Command timed out after {timeout} seconds.",
] if item
],
"main_blocker": text(
user_language,
f"Selected documented command did not finish within {timeout} seconds.",
f"选定的文档命令未在 {timeout} 秒内完成。",
),
"execution_mode": shell_mode,
**runtime_metadata(result),
}
combined: List[str] = []
if result.get("stdout", "").strip():
combined.append("STDOUT:\n" + result["stdout"].strip())
if result.get("stderr", "").strip():
combined.append("STDERR:\n" + result["stderr"].strip())
metric_data = parse_observed_metrics("\n".join([result.get("stdout", ""), result.get("stderr", "")]))
if result.get("returncode") == 0:
return {
"status": "success",
"documented_command_status": "success",
"execution_log": combined,
"main_blocker": text(user_language, "None.", "无。"),
"execution_mode": shell_mode,
**runtime_metadata(result),
**metric_data,
}
return {
"status": "partial",
"documented_command_status": "partial",
"execution_log": combined,
**metric_data,
"execution_mode": shell_mode,
**runtime_metadata(result),
"main_blocker": text(
user_language,
f"Selected documented command exited with code {result.get('returncode')}.",
f"选定的文档命令以退出码 {result.get('returncode')} 结束。",
),
}
def runtime_metadata(result: Dict[str, Any]) -> Dict[str, Any]:
keys = [
"runtime_run_id",
"runtime_dir",
"runtime_status",
"runtime_state_path",
"runtime_events_path",
"stdout_log_path",
"stderr_log_path",
"stdout_truncated",
"stderr_truncated",
"cancelled",
"duration_seconds",
"runtime_attempt",
"runtime_retry_of",
"resources_log_path",
"resource_summary",
"model_adapter",
]
return {key: result.get(key) for key in keys}
def maybe_run_training(
*,
repo_path: Path,
command: str,
train_script: Path,
lane: str,
user_language: str,
full_training_authorized: bool,
train_timeout: int,
dataset_hint: str,
checkpoint_hint: str,
resume_from: str,
max_train_steps: int,
shell_mode: str,
runtime_root: Path,
model_profile_json: str,
required_model_capabilities: List[str],
gpu_monitor_enabled: bool,
) -> Dict[str, Any]:
if not command:
return {
"status": "not_run",
"documented_command_status": "not_run",
"execution_log": [],
"main_blocker": text(
user_language,
"No documented training command was extracted from README.",
"README 中未提取到已文档化训练命令。",
),
"lane": lane,
"run_mode": "startup_verification" if lane == "trusted" else "full_kickoff",
"resume_from": resume_from or None,
"dataset": dataset_hint,
"checkpoint_source": checkpoint_hint,
"max_steps": max_train_steps,
"completed_steps": 0,
"best_metric": None,
"best_checkpoint": None,
"stop_reason": "not_run",
"last_epoch": None,
"last_step": None,
"observed_metrics": {},
"checkpoint_candidates": [],
"monitoring_scope": "not_run",
"execution_mode": shell_mode,
}
if resume_from:
run_mode = "resume"
elif lane == "trusted" and not full_training_authorized:
run_mode = "startup_verification"
else:
run_mode = "full_kickoff"
training_args = [
"--repo",
str(repo_path),
"--command",
command,
"--timeout",
str(train_timeout),
"--lane",
lane,
"--run-mode",
run_mode,
"--dataset",
dataset_hint,
"--checkpoint-source",
checkpoint_hint,
"--resume-from",
resume_from,
"--max-steps",
str(max_train_steps),
"--shell-mode",
shell_mode,
"--runtime-root",
str(runtime_root),
]
if model_profile_json:
training_args.extend(["--model-profile-json", model_profile_json])
for capability in required_model_capabilities:
training_args.extend(["--require-model-capability", capability])
if not gpu_monitor_enabled:
training_args.append("--no-gpu-monitor")
return run_json(train_script, training_args)
def build_context(
*,
chosen: Dict[str, Any],
repo_path: Path,
scan_data: Dict[str, Any],
command_data: Dict[str, Any],
setup_plan: Dict[str, Any],
asset_data: Dict[str, Any],
run_data: Dict[str, Any],
user_language: str,
run_selected: bool,
include_analysis_pass: bool,
include_paper_gap: bool,
lane: str,
full_training_authorized: bool,
stage_results: List[Dict[str, Any]],
) -> Dict[str, Any]:
skill_chain = plan_skill_chain(chosen["selected_goal"], include_analysis_pass, include_paper_gap)
execution_skill = "run-train" if chosen["selected_goal"] == "training" else "minimal-run-and-audit"
status = run_data["status"] if run_selected else "not_run"
metric_acceptance_failed = (
run_selected
and run_data.get("runtime_status") == "success"
and run_data.get("result_match", {}).get("status") == "mismatched"
)
documented_status = (
run_data["documented_command_status"]
if run_selected
else ("not_run" if not chosen["documented_command"] else "documented")
)
structure = scan_data.get("structure", {})
# Intake plans are not execution evidence, even when their provenance is documented.
setup_commands = [dict(item, execution_status="not_run") for item in setup_plan.get("setup_commands", [])]
asset_commands = build_asset_commands(asset_data, user_language)
setup_advisories = list(setup_plan.get("unresolved_setup_risks", []))
dataset_hint = run_data.get("dataset") or derive_dataset_hint(asset_data)
checkpoint_hint = run_data.get("checkpoint_source") or derive_checkpoint_hint(asset_data)
training_duration_hint = (
estimate_training_duration(repo_path, chosen["documented_command"], int(run_data.get("max_steps") or 0))
if chosen["selected_goal"] == "training" and chosen["documented_command"]
else None
)
notes: List[str] = []
notes.extend(scan_data.get("warnings", []))
notes.extend(command_data.get("warnings", []))
notes.extend(setup_plan.get("setup_notes", []))
notes.extend(run_data.get("execution_log", []))
assumptions = [
"README remains the primary source of truth.",
"Environment creation should prefer isolated setup before any semantic code changes.",
"Model architecture should remain unchanged unless the researcher explicitly requests otherwise.",
]
if chosen["selected_goal"] == "training" and lane == "trusted" and not full_training_authorized:
assumptions.append("Only startup verification is allowed before the researcher explicitly authorizes a fuller training reproduction run.")
unverified_inferences = [
"Asset and dataset hints remain conservative until the repo or README confirms the exact path layout."
]
protocol_deviations: List[str] = []
human_decisions_required: List[str] = []
if not chosen["documented_command"]:
result_summary = text(
user_language,
"No documented runnable command was extracted. Repo intake was completed.",
"未提取到可运行的文档命令,已完成仓库 intake。",
)
elif chosen["selected_goal"] != "training":
result_summary = text(
user_language,
f"Selected goal `{chosen['selected_goal']}` from README evidence.",
f"已根据 README 证据选择目标 `{chosen['selected_goal']}`。",
)
else:
result_summary = text(
user_language,
"Selected the documented training command after no smaller inference or evaluation target was available.",
"在没有更小的推理或评测目标时,已选择文档中的训练命令。",
)
if run_selected:
if status == "success":
result_summary = text(user_language, "Selected documented command finished successfully.", "选定的文档命令已成功完成。")
elif metric_acceptance_failed:
result_summary = text(
user_language,
"The documented command completed, but explicit metric acceptance failed; this is not a successful reproduction result.",
"文档命令已完成,但显式指标验收未通过;本次结果不能视为复现成功。",
)
elif status == "partial":
result_summary = (
text(
user_language,
"Selected training command produced early training evidence within the current monitoring window.",
"选定的训练命令已在当前监控窗口内产生早期训练证据。",
)
if chosen["selected_goal"] == "training"
else text(
user_language,
"Selected documented command started but did not complete cleanly.",
"选定的文档命令已启动,但未完整成功结束。",
)
)
elif status == "blocked":
result_summary = (
text(user_language, "Selected training command could not be launched.", "选定的训练命令无法启动。")
if chosen["selected_goal"] == "training"
else text(user_language, "Selected documented command could not be launched.", "选定的文档命令无法启动。")
)
section = chosen.get("documented_command_section")
command_notes = [
text(
user_language,
f"README path: {scan_data.get('readme_path') or 'not found'}",
f"README 路径:{scan_data.get('readme_path') or 'not found'}",
),
text(
user_language,
f"Detected top-level entries: {', '.join(structure.get('top_level', [])) or 'none'}",
f"检测到的顶层条目:{', '.join(structure.get('top_level', [])) or 'none'}",
),
]
if setup_plan.get("environment_file"):
command_notes.append(f"Environment plan source: {setup_plan['environment_file']}")
command_notes.extend(setup_plan.get("setup_notes", []))
if chosen["documented_command"]:
source_note = text(
user_language,
f"Main run label: documented from README ({chosen.get('command_source', 'readme')})",
f"主运行标签:来自 README 的 documented({chosen.get('command_source', 'readme')})",
)
if section:
source_note += text(user_language, f", section `{section}`", f",章节 `{section}`")
command_notes.append(source_note)
command_notes.append(f"Planned skill chain: {', '.join(skill_chain)}")
# Setup discovery gaps are advisory until a selected action actually needs them.
# Preserve every gap separately; an observed execution failure still requires review below.
if chosen.get("requires_substitution"):
human_decisions_required.append(text(user_language,
"Substitute the placeholder values (<...>) in the selected documented command before execution.",
"执行前请将选定文档命令中的占位符(<...>)替换为真实值。"))
if not chosen["documented_command"]:
human_decisions_required.append(text(user_language,
"Select or confirm a documented runnable command before treating this as a reproduction run.",
"先选择或确认可运行的文档命令,才能将本次操作视为复现执行。"))
if chosen["selected_goal"] == "training" and lane == "trusted" and not full_training_authorized:
human_decisions_required.append(text(user_language,
"Review the startup verification evidence and confirm whether to continue with a fuller training reproduction run.",
"检查启动验证证据,并确认是否继续更完整的训练复现。"))
if metric_acceptance_failed:
human_decisions_required.append(text(user_language,
"Review missing or out-of-tolerance metrics against the recorded expectations and experiment protocol before accepting the result or changing the command.",
"接受结果或调整命令前,请按已记录的期望值和实验协议检查缺失或超出容差的指标。"))
elif run_selected and status in {"partial", "blocked"}:
human_decisions_required.append(text(user_language,
"Review the blocker before adapting commands, dependencies, or protocol-sensitive settings.",
"调整命令、依赖或影响实验协议的设置前,请先检查阻塞原因。"))
if include_paper_gap:
human_decisions_required.append(text(user_language,
"Provide a narrow paper question and an authoritative paper source before running paper-context-resolver.",
"运行 paper-context-resolver 前,请提供具体的论文问题与权威论文来源。"))
if metric_acceptance_failed:
next_action = text(
user_language,
"Inspect `status.json.result_match` and the raw logs, then check metric names, data, preprocessing, checkpoints, and evaluation conditions. Do not widen tolerances or change expected values merely to obtain a pass.",
"检查 `status.json.result_match` 和原始日志,再核对指标名称、数据、预处理、权重与评测条件。不要仅为通过验收而放宽容差或更改期望值。",
)
next_safe_action = text(
user_language,
"Preserve the failed acceptance evidence and review the mismatch before any retry or protocol change; command completion alone does not satisfy the expected result.",
"保留验收失败证据,在重试或修改实验协议前检查不匹配原因;命令完成本身不代表已达到期望结果。",
)
elif chosen["selected_goal"] == "training":
if lane == "trusted" and not full_training_authorized:
next_action = text(
user_language,
f"Review `train_outputs/status.json`, then decide whether to authorize a fuller training reproduction run. Planned command: `{chosen['documented_command']}`. Estimated duration: {training_duration_hint}.",
f"先检查 `train_outputs/status.json`,再决定是否授权更完整的训练复现。计划继续执行的命令是:`{chosen['documented_command']}`。保守预估时长:{training_duration_hint}。",
)
next_safe_action = "Keep the repo unchanged, review startup evidence, and only continue with fuller training after explicit researcher approval."
elif lane == "explore":
next_action = text(
user_language,
"Review the recorded training evidence and continue isolated exploratory training if the variant still looks promising.",
"先检查已记录的训练证据,如该变体仍有希望,再继续隔离的探索训练。",
)
next_safe_action = "Keep exploratory changes isolated and compare the recorded early metrics before widening the search."
else:
next_action = text(
user_language,
"Review the current training record and continue monitoring or resume from the latest checkpoint if needed.",
"先检查当前训练记录,如有需要,再继续监控或从最新 checkpoint 恢复。",
)
next_safe_action = "Preserve the documented training semantics and continue from recorded checkpoints only if the current run remains faithful."
else:
next_action = (
text(user_language, "Prepare environment and assets, then retry the documented command.", "先准备环境与资源,再重试该文档命令。")
if status in {"partial", "blocked", "not_run"}
else text(user_language, "Review outputs and continue with the next documented verification step.", "检查输出后继续下一步文档化验证。")
)
next_safe_action = (
"Review setup assumptions and confirm the next documented command before making any semantic changes."
if status in {"partial", "blocked", "not_run"}
else "Review generated outputs and confirm that the next documented verification step preserves experiment meaning."
)
run_execution_status = run_data.get("runtime_status") or "not_run"
run_commands = ([{
"label": "documented", "command": chosen["documented_command"],
"execution_status": run_execution_status,
"execution_evidence": run_data.get("runtime_state_path"),
}] if chosen["documented_command"] else [])
verification_commands = (
[{"label": "inferred", "command": "python - <<'PY'\nimport pathlib\nprint(pathlib.Path('train_outputs/status.json').exists())\nPY"}]
if chosen["selected_goal"] == "training"
else []
)
if chosen["selected_goal"] != "training":
comparison_status = run_data.get("result_match", {}).get("status", "not_evaluated")
command_notes.append(text(
user_language,
f"No separate verification command was executed. Built-in metric comparison: `{comparison_status}`; inspect `status.json.result_match` for expected values and tolerance.",
f"未执行单独的验证命令。内置指标比较状态为 `{comparison_status}`;期望值与容差见 `status.json.result_match`。",
))
for item in verification_commands:
item["execution_status"] = "not_run"
command_reporting = {
"setup": "not_run", "assets": "not_run",
"main_run": run_execution_status, "verification": "not_run",
}
evidence = [
text(
user_language,
f"Detected files: {', '.join(scan_data.get('detected_files', [])) or 'none'}",
f"检测到的文件:{', '.join(scan_data.get('detected_files', [])) or 'none'}",
),
text(
user_language,
f"Command categories: {json.dumps(command_data.get('counts', {}), ensure_ascii=False)}",
f"命令分类:{json.dumps(command_data.get('counts', {}), ensure_ascii=False)}",
),
text(
user_language,
f"Selected command kind: {chosen.get('documented_command_kind', 'none')}",
f"已选命令类型:{chosen.get('documented_command_kind', 'none')}",
),
]
if setup_plan.get("environment_file"):
evidence.append(f"Environment file: {setup_plan['environment_file']}")
if asset_data.get("text_hints"):
evidence.append(f"Asset hints detected: {len(asset_data['text_hints'])}")
timeline = [
text(user_language, "Scanned repository structure and key metadata files.", "已扫描仓库结构和关键元数据文件。"),
text(user_language, "Extracted README code blocks and shell-like commands.", "已提取 README 中的代码块和 shell 风格命令。"),
text(user_language, f"Selected `{chosen['selected_goal']}` as the smallest trustworthy target.", f"已将 `{chosen['selected_goal']}` 选为最小可信目标。"),
text(user_language, "Prepared conservative setup and asset assumptions.", "已准备保守的环境与资源假设。"),
text(user_language, "Execution step was skipped." if not run_selected else "Attempted the selected documented command.", "执行步骤已跳过。" if not run_selected else "已尝试选定的文档命令。"),
]
if chosen["selected_goal"] == "training":
timeline.append(text(user_language, f"Training lane `{lane}` selected with run mode `{run_data.get('run_mode', 'startup_verification')}`.", f"已选择训练 lane `{lane}`,运行模式为 `{run_data.get('run_mode', 'startup_verification')}`。"))
if training_duration_hint:
timeline.append(text(user_language, f"Estimated fuller training duration: {training_duration_hint}.", f"保守估计完整训练时长:{training_duration_hint}。"))
artifact_provenance = [
{"artifact": "readme", "source": scan_data.get("readme_path") or "not found", "kind": "repo_file"},
{"artifact": "documented_command", "source": chosen.get("command_source", "none"), "kind": "readme_extraction"},
{"artifact": "environment_plan", "source": setup_plan.get("environment_file") or "inferred", "kind": "setup_plan"},
{"artifact": "asset_manifest", "source": "artifacts/assets/asset_manifest.json", "kind": "generated"},
{"artifact": "output_dir", "source": "repro_outputs/", "kind": "generated"},
]
if chosen["selected_goal"] == "training":
artifact_provenance.append({"artifact": "train_outputs", "source": "train_outputs/", "kind": "generated"})
return {
"schema_version": "1.0",
"generated_at": scan_data.get("generated_at"),
"user_language": user_language,
"target_repo": str(repo_path.resolve()),
"readme_first": True,
"lane": lane,
"selected_goal": chosen["selected_goal"],
"goal_priority": chosen["goal_priority"],
"execution_skill": execution_skill,
"planned_skill_chain": skill_chain,
"stage_results": stage_results,
"status": status,
"documented_command_status": documented_status,
"documented_command": chosen["documented_command"] or "None extracted",
"documented_command_kind": chosen.get("documented_command_kind", "none"),
"documented_command_source": chosen.get("command_source", "none"),
"documented_command_section": chosen.get("documented_command_section"),
"documented_command_source_file": chosen.get("documented_command_source_file"),
"requires_substitution": bool(chosen.get("requires_substitution")),
"goal_candidates": chosen.get("goal_candidates", []),
"evidence_level": "direct" if chosen["documented_command"] else "mixed",
"result_summary": result_summary,
"main_blocker": run_data.get("main_blocker", text(user_language, "No blocker recorded.", "未记录阻塞项。")),
"next_action": next_action,
"next_safe_action": next_safe_action,
"setup_commands": setup_commands,
"asset_commands": asset_commands,
"run_commands": run_commands,
"verification_commands": verification_commands,
"command_notes": command_notes,
"command_reporting": command_reporting,
"setup_advisories": setup_advisories,
"timeline": timeline,
"assumptions": assumptions,
"unverified_inferences": unverified_inferences,
"evidence": evidence,
"blockers": [run_data.get("main_blocker", text(user_language, "None.", "无。"))],
"protocol_deviations": protocol_deviations,
"human_decisions_required": human_decisions_required,
"artifact_provenance": artifact_provenance,
"notes": notes,
"patches_applied": False,
"patch_branch": "",
"readme_fidelity": "preserved",
"highest_patch_risk": "low",
"verified_commits": [],
"validation_summary": "",
"patch_notes": [],
"full_training_authorized": full_training_authorized,
"requires_full_training_confirmation": chosen["selected_goal"] == "training" and lane == "trusted" and not full_training_authorized,
"run_mode": run_data.get("run_mode", "startup_verification" if chosen["selected_goal"] == "training" else None),
"resume_from": run_data.get("resume_from"),
"dataset": dataset_hint,
"checkpoint_source": checkpoint_hint,
"full_training_command": chosen["documented_command"] if chosen["selected_goal"] == "training" else None,
"training_duration_hint": training_duration_hint,
"max_steps": run_data.get("max_steps"),
"completed_steps": run_data.get("completed_steps"),
"best_metric": run_data.get("best_metric"),
"best_checkpoint": run_data.get("best_checkpoint"),
"stop_reason": run_data.get("stop_reason"),
"last_epoch": run_data.get("last_epoch"),
"last_step": run_data.get("last_step"),
"observed_metrics": run_data.get("observed_metrics", {}),
"result_match": run_data.get("result_match", {"status": "not_evaluated"}),
"checkpoint_candidates": run_data.get("checkpoint_candidates", []),
"monitoring_scope": run_data.get("monitoring_scope"),
"execution_mode": run_data.get("execution_mode", "direct"),
"runtime_run_id": run_data.get("runtime_run_id"),
"runtime_dir": run_data.get("runtime_dir"),
"runtime_status": run_data.get("runtime_status"),
"runtime_state_path": run_data.get("runtime_state_path"),
"runtime_events_path": run_data.get("runtime_events_path"),
"stdout_log_path": run_data.get("stdout_log_path"),
"stderr_log_path": run_data.get("stderr_log_path"),
"stdout_truncated": run_data.get("stdout_truncated", False),
"stderr_truncated": run_data.get("stderr_truncated", False),
"cancelled": run_data.get("cancelled", False),
"duration_seconds": run_data.get("duration_seconds"),
"runtime_attempt": run_data.get("runtime_attempt"),
"runtime_retry_of": run_data.get("runtime_retry_of"),
"resources_log_path": run_data.get("resources_log_path"),
"resource_summary": run_data.get("resource_summary", {}),
"model_adapter": run_data.get("model_adapter"),
}
def main() -> int:
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(description="Run a minimal README-first reproduction orchestration.")
parser.add_argument("--repo", required=True, help="Path to the target repository.")
parser.add_argument("--output-dir", default="repro_outputs", help="Directory to write standardized outputs into.")
parser.add_argument("--source-adjacent-readme", action="store_true", help="Also write an owned RIGORPILOT_README.md beside the original README, preserving relative media paths.")
parser.add_argument("--train-output-dir", default="", help="Optional override for the supplemental training output directory.")
parser.add_argument(
"--runtime-root",
default="",
help="Optional runtime state root (default: <output-dir>/_runtime).",
)
parser.add_argument("--model-profile-json", default="", help="Optional provider-neutral model identity/capability profile.")
parser.add_argument(
"--require-model-capability",
action="append",
default=[],
help="Required model capability; repeat as needed.",
)
parser.add_argument("--monitor-gpu", action="store_true", help="Sample NVIDIA telemetry for non-training commands too.")
parser.add_argument("--no-gpu-monitor", action="store_true", help="Disable NVIDIA telemetry for training commands.")
parser.add_argument("--user-language", default="en", help="Language tag for human-readable reports.")
parser.add_argument("--run-selected", action="store_true", help="Execute the selected documented command.")
parser.add_argument("--include-analysis-pass", action="store_true", help="Run analyze-project and record its outputs in the stage ledger.")
parser.add_argument(
"--include-paper-gap",
action="store_true",
help="Request paper-context-resolver; records a blocked stage until a narrow question and source are supplied.",
)
parser.add_argument("--timeout", type=int, default=120, help="Execution timeout in seconds for non-training documented commands.")
parser.add_argument("--train-timeout", type=int, default=120, help="Monitoring timeout in seconds for training commands.")
parser.add_argument("--lane", choices=["trusted", "explore"], default="trusted", help="Execution lane policy.")
parser.add_argument("--full-training-authorized", action="store_true", help="Allow the orchestrator to proceed beyond startup verification for training.")
parser.add_argument("--resume-from", default="", help="Optional checkpoint path to pass through to run-train.")
parser.add_argument("--max-train-steps", type=int, default=0, help="Optional expected max train steps for reporting.")
parser.add_argument(
"--expected-metric",
action="append",
default=[],
metavar="NAME=VALUE",
help="Explicit expected metric for result matching. Repeat for multiple metrics.",
)
parser.add_argument(
"--metric-absolute-tolerance",
type=float,
default=0.0,
help="Maximum absolute error allowed for every --expected-metric value.",
)
parser.add_argument(
"--shell-mode",
choices=["direct", "native"],
default="direct",
help="Use direct argv execution by default; native shell execution requires explicit opt-in after review.",
)
args = parser.parse_args()
if args.timeout <= 0 or args.train_timeout <= 0:
parser.error("--timeout and --train-timeout must be greater than zero")
if args.metric_absolute_tolerance < 0 or not math.isfinite(args.metric_absolute_tolerance):
parser.error("--metric-absolute-tolerance must be a finite non-negative number")
try:
expected_metrics = parse_expected_metrics(args.expected_metric)
except ValueError as exc:
parser.error(str(exc))
repo_path = Path(args.repo).resolve()
source_skills_dir = Path(__file__).resolve().parents[2]
bundled_skills_dir = SKILL_ROOT / "_bundled" / "skills"
base_dir = (
source_skills_dir
if (source_skills_dir / "repo-intake-and-plan" / "scripts" / "scan_repo.py").is_file()
else bundled_skills_dir
)
scan_script = base_dir / "repo-intake-and-plan" / "scripts" / "scan_repo.py"
extract_script = base_dir / "repo-intake-and-plan" / "scripts" / "extract_commands.py"
setup_script = base_dir / "env-and-assets-bootstrap" / "scripts" / "plan_setup.py"
asset_script = base_dir / "env-and-assets-bootstrap" / "scripts" / "prepare_assets.py"
repro_write_script = base_dir / "minimal-run-and-audit" / "scripts" / "write_outputs.py"
train_write_script = base_dir / "run-train" / "scripts" / "write_outputs.py"
train_execute_script = base_dir / "run-train" / "scripts" / "run_training.py"
analyze_script = base_dir / "analyze-project" / "scripts" / "analyze_project.py"
scan_data = run_json(scan_script, ["--repo", str(repo_path), "--json"])
readme_path = scan_data.get("readme_path")
command_data: Dict[str, Any] = {"commands": [], "counts": {}, "warnings": []}
if readme_path:
command_data = run_json(extract_script, ["--readme", readme_path, "--json"])
command_data = delegate_to_docs(readme_path, extract_script, command_data)
output_dir = Path(args.output_dir).resolve()
train_output_dir = Path(args.train_output_dir).resolve() if args.train_output_dir else output_dir.parent / "train_outputs"
runtime_root = Path(args.runtime_root).resolve() if args.runtime_root else output_dir / "_runtime"
try:
model_adapter = load_model_profile(Path(args.model_profile_json) if args.model_profile_json else None)
missing_model_capabilities = missing_capabilities(model_adapter, args.require_model_capability)
except ModelAdapterError as exc:
parser.error(str(exc))
if missing_model_capabilities:
parser.error(f"model profile is missing required capabilities: {', '.join(missing_model_capabilities)}")
assets_root = output_dir.parent / "artifacts" / "assets"
asset_manifest_path = assets_root / "asset_manifest.json"
setup_plan = run_json(setup_script, ["--repo", str(repo_path), "--json"])
asset_data = run_json(
asset_script,
[
"--repo",
str(repo_path),
"--assets-root",
str(assets_root),
"--output-json",
str(asset_manifest_path),
],
)
stage_results: List[Dict[str, Any]] = [
{
"stage": "repo-intake-and-plan",
"status": "success",
"detail": "Repository metadata and README commands were inspected.",
},
{
"stage": "env-and-assets-bootstrap",
"status": "success",
"detail": "Setup plan and asset manifest were generated without installing dependencies.",
"outputs": [str(asset_manifest_path)],
},
]
if args.include_analysis_pass:
analysis_output_dir = output_dir.parent / "analysis_outputs"
try:
run_json(
analyze_script,
["--repo", str(repo_path), "--output-dir", str(analysis_output_dir)],
)
stage_results.append(
{
"stage": "analyze-project",
"status": "success",
"detail": "Read-only project analysis completed.",
"outputs": [str(analysis_output_dir / "status.json")],
}
)
except (OSError, subprocess.CalledProcessError, json.JSONDecodeError) as exc:
stage_results.append(
{
"stage": "analyze-project",
"status": "blocked",
"detail": f"Read-only project analysis failed: {type(exc).__name__}: {exc}",
}
)
chosen = choose_goal(command_data.get("commands", []), repo_path)
dataset_hint = derive_dataset_hint(asset_data)
checkpoint_hint = derive_checkpoint_hint(asset_data)
run_data: Dict[str, Any] = {
"status": "not_run",
"documented_command_status": "not_run",
"execution_log": [],
"main_blocker": text(args.user_language, "Execution was not requested.", "未请求执行。"),
"lane": args.lane,
"run_mode": "startup_verification" if chosen["selected_goal"] == "training" and args.lane == "trusted" and not args.full_training_authorized else ("full_kickoff" if chosen["selected_goal"] == "training" else None),
"resume_from": args.resume_from or None,
"dataset": dataset_hint,
"checkpoint_source": checkpoint_hint,
"max_steps": args.max_train_steps,
"completed_steps": 0,
"best_metric": None,
"best_checkpoint": None,
"stop_reason": "not_run" if chosen["selected_goal"] == "training" else None,
"last_epoch": None,
"last_step": None,
"observed_metrics": {},
"result_match": {"status": "not_evaluated", "reason": "Execution was not requested.", "comparisons": []},
"checkpoint_candidates": [],
"monitoring_scope": "not_run",
"execution_mode": args.shell_mode,
"model_adapter": model_adapter,
}
if args.run_selected and chosen.get("requires_substitution"):
run_data["status"] = "not_run"
run_data["documented_command_status"] = "not_run"
run_data["main_blocker"] = text(
args.user_language,
"Documented command contains placeholder values (<...>); substitute them before execution.",
"文档命令包含占位符(<...>),需要先替换为真实值再执行。",
)
elif args.run_selected:
if chosen["selected_goal"] == "training":
run_data = maybe_run_training(
repo_path=repo_path,
command=chosen["documented_command"],
train_script=train_execute_script,
lane=args.lane,
user_language=args.user_language,
full_training_authorized=args.full_training_authorized,
train_timeout=args.train_timeout,
dataset_hint=dataset_hint,
checkpoint_hint=checkpoint_hint,
resume_from=args.resume_from,
max_train_steps=args.max_train_steps,
shell_mode=args.shell_mode,
runtime_root=runtime_root,
model_profile_json=args.model_profile_json,
required_model_capabilities=args.require_model_capability,
gpu_monitor_enabled=not args.no_gpu_monitor,
)
else:
run_data = maybe_run_command(
repo_path,
chosen["documented_command"],
args.timeout,
args.user_language,
args.shell_mode,
runtime_root,
model_adapter,
args.monitor_gpu,
)
run_data["result_match"] = compare_expected_metrics(
run_data.get("observed_metrics", {}),
expected_metrics,
args.metric_absolute_tolerance,
)
if (
args.run_selected
and run_data.get("runtime_status") == "success"
and run_data.get("status") in {"success", "partial"}
and run_data["result_match"]["status"] == "mismatched"
):
# Keep command/runtime success as process evidence, but never let exit 0
# override an explicitly failed result criterion in the overall outcome.
run_data["status"] = "partial"
run_data["main_blocker"] = text(
args.user_language,
"Explicit metric acceptance failed: at least one expected metric is missing or outside the configured absolute tolerance. Inspect `status.json.result_match` for per-metric evidence.",
"显式指标验收未通过:至少一个期望指标缺失或超出设定的绝对容差。逐项证据见 `status.json.result_match`。",
)
execution_stage = "run-train" if chosen["selected_goal"] == "training" else "minimal-run-and-audit"
stage_results.append(
{
"stage": execution_stage,
"status": run_data["status"] if args.run_selected else "not_requested",
"detail": (
"Selected documented command was attempted."
if args.run_selected
else "Execution was not requested; no command was run."
),
}
)
if args.include_paper_gap:
stage_results.append(
{
"stage": "paper-context-resolver",
"status": "blocked",
"detail": "A narrow paper question and authoritative paper source were not supplied.",
}
)
context = build_context(
chosen=chosen,
repo_path=repo_path,
scan_data=scan_data,
command_data=command_data,
setup_plan=setup_plan,
asset_data=asset_data,
run_data=run_data,
user_language=args.user_language,
run_selected=args.run_selected,
include_analysis_pass=args.include_analysis_pass,
include_paper_gap=args.include_paper_gap,
lane=args.lane,
full_training_authorized=args.full_training_authorized,
stage_results=stage_results,
)
context["annotated_readme"] = None
context["readme_section_coverage"] = {}
context["source_adjacent_readme"] = {"status": "not_requested", "path": None}
if readme_path and Path(readme_path).exists():
annotated_path, coverage = write_annotated_readme(
readme_path=Path(readme_path),
context={
**context,
"readme_commands": command_data.get("commands", []),
"execution_log": run_data.get("execution_log", []),
"local_dataset_present": any(
item.get("asset_group") in {"datasets", "data"} and item.get("status") == "present"
for item in asset_data.get("manifest", [])
),
},
output_path=output_dir / "ANNOTATED_README.md",
source_adjacent=args.source_adjacent_readme,
train_output_dir=train_output_dir,
)
context["annotated_readme"] = str(annotated_path)
context["readme_section_coverage"] = coverage
context["source_adjacent_readme"] = coverage["source_adjacent_readme"]
elif args.source_adjacent_readme:
context["source_adjacent_readme"] = {"status": "blocked", "path": None, "reason": "No source README was found; standard evidence retained."}
write_bundle(repro_write_script, output_dir, context)
if context["selected_goal"] == "training":
write_bundle(train_write_script, train_output_dir, context)
context["lesson_recorded"] = maybe_record_lesson(repo_path, context) if args.run_selected else None
print(json.dumps(context, indent=2, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())