def expected_calibration_error(probs: np.ndarray, labels: np.ndarray, n_bins: int = 10) -> float:
	bins = np.linspace(0.0, 1.0, n_bins + 1)
	ece = 0.0
	for i in range(n_bins):
		lo, hi = bins[i], bins[i + 1]
		mask = (probs >= lo) & (probs < hi)
		if not np.any(mask):
			continue
		conf = probs[mask].mean()
		acc = labels[mask].mean()
		ece += (mask.mean()) * abs(acc - conf)
	return float(ece)

def brier_score(probs: np.ndarray, labels: np.ndarray) -> float:
	probs = probs.astype(float)
	labels = labels.astype(float)
	return float(np.mean((probs - labels) ** 2))

def train_val_split(n: int, val_ratio: float = 0.2, seed: int = 42) -> Tuple[np.ndarray, np.ndarray]:
	rng = np.random.RandomState(seed)
	idx = np.arange(n)
	rng.shuffle(idx)
	val_n = max(1, int(n * val_ratio)) if n > 1 else 1
	val_idx = idx[:val_n]
	train_idx = idx[val_n:]
	if train_idx.size == 0:
		train_idx = val_idx
	return train_idx, val_idx

def _enrich_text(inp: Dict[str, str], enable: bool) -> str:
	if not enable:
		return " \n ".join([inp.get("obs", ""), inp.get("plan", ""), inp.get("action", ""), inp.get("effects", "")])
	# Domain-aware tokens: extract hints from obs/plan/action JSON strings
	obs = inp.get("obs", "")
	plan = inp.get("plan", "")
	action = inp.get("action", "")
	effects = inp.get("effects", "")
	# Best-effort cheap tokens
	tokens: List[str] = []
	try:
		if "browser.read" in action or "url" in action:
			tokens.append("__DOM__")
			# URL and selector snippets
			import re  # type: ignore
			m = re.search(r"https?://[^\"'\s]+", action)
			if m:
				tokens.append("URL=" + m.group(0)[:64])


def brier_score(probs: np.ndarray, labels: np.ndarray) -> float:
	probs = probs.astype(float)
	labels = labels.astype(float)
	return float(np.mean((probs - labels) ** 2))

def train_val_split(n: int, val_ratio: float = 0.2, seed: int = 42) -> Tuple[np.ndarray, np.ndarray]:
	rng = np.random.RandomState(seed)
	idx = np.arange(n)
	rng.shuffle(idx)
	val_n = max(1, int(n * val_ratio)) if n > 1 else 1
	val_idx = idx[:val_n]
	train_idx = idx[val_n:]
	if train_idx.size == 0:
		train_idx = val_idx
	return train_idx, val_idx

def _enrich_text(inp: Dict[str, str], enable: bool) -> str:
	if not enable:
		return " \n ".join([inp.get("obs", ""), inp.get("plan", ""), inp.get("action", ""), inp.get("effects", "")])
	# Domain-aware tokens: extract hints from obs/plan/action JSON strings
	obs = inp.get("obs", "")
	plan = inp.get("plan", "")
	action = inp.get("action", "")
	effects = inp.get("effects", "")
	# Best-effort cheap tokens
	tokens: List[str] = []
	try:
		if "browser.read" in action or "url" in action:
			tokens.append("__DOM__")
			# URL and selector snippets
			import re  # type: ignore
			m = re.search(r"https?://[^\"'\s]+", action)
			if m:
				tokens.append("URL=" + m.group(0)[:64])
			m2 = re.search(r"selector\":\s*\"([^\"]+)\"", action)
			if m2:
				tokens.append("SEL=" + m2.group(1)[:64])
	except Exception:
		pass
	try:
		if "argv" in action or "tool" in action:
			tokens.append("__CLI__")
			# argv0
			import re  # type: ignore
			m = re.search(r"\[\s*\"([^\"]+)\"", action)
			if m:
				tokens.append("ARGV0=" + m.group(1)[:32])
	except Exception:
		pass
	return " \n ".join(tokens + [obs, plan, action, effects])

def main() -> int:
	ap = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[2]
	ap.add_argument("--data", default=str(root / "data" / "skills" / "wm_ds.jsonl"))
	ap.add_argument("--out", default=str(root / "models" / "wm_mlp"))
	ap.add_argument("--val-ratio", type=float, default=0.2)
	ap.add_argument("--domain-feats", action="store_true", help="Enrich training text with domain tokens (tool/url/selector/argv)")
	args = ap.parse_args()

	data_path = Path(args.data)
	rows = load_jsonl(data_path)
	if not rows:
		print("No data found for WM training.")
		return 0

	texts: List[str] = []
	y_success: List[int] = []
	y_risk: List[float] = []


main

			if m:
				tokens.append("URL=" + m.group(0)[:64])
			m2 = re.search(r"selector\":\s*\"([^\"]+)\"", action)
			if m2:
				tokens.append("SEL=" + m2.group(1)[:64])
	except Exception:
		pass
	try:
		if "argv" in action or "tool" in action:
			tokens.append("__CLI__")
			# argv0
			import re  # type: ignore
			m = re.search(r"\[\s*\"([^\"]+)\"", action)
			if m:
				tokens.append("ARGV0=" + m.group(1)[:32])
	except Exception:
		pass
	return " \n ".join(tokens + [obs, plan, action, effects])

def main() -> int:
	ap = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[2]
	ap.add_argument("--data", default=str(root / "data" / "skills" / "wm_ds.jsonl"))
	ap.add_argument("--out", default=str(root / "models" / "wm_mlp"))
	ap.add_argument("--val-ratio", type=float, default=0.2)
	ap.add_argument("--domain-feats", action="store_true", help="Enrich training text with domain tokens (tool/url/selector/argv)")
	args = ap.parse_args()

	data_path = Path(args.data)
	rows = load_jsonl(data_path)
	if not rows:
		print("No data found for WM training.")
		return 0

	texts: List[str] = []
	y_success: List[int] = []
	y_risk: List[float] = []
	for r in rows:
		inp = r.get("input", {})
		# inp may also include effects
		txt = _enrich_text({
			"obs": inp.get("obs", ""),
			"plan": inp.get("plan", ""),
			"action": inp.get("action", ""),
			"effects": inp.get("effects", ""),
		}, enable=bool(args.domain_feats))
		texts.append(txt)
		y_success.append(int(r.get("success", 0)))
		y_risk.append(float(r.get("risk", 0.5)))

	X_all = texts
	y_s = np.array(y_success)
	y_r = np.array(y_risk)
	n = len(X_all)

	vec = TfidfVectorizer(max_features=5000, ngram_range=(1, 2))
	X = vec.fit_transform(X_all)

	# Split
	tr_idx, va_idx = train_val_split(n, val_ratio=float(args.val_ratio))
	X_tr, X_va = X[tr_idx], X[va_idx]
	y_s_tr, y_s_va = y_s[tr_idx], y_s[va_idx]
	y_r_tr, y_r_va = y_r[tr_idx], y_r[va_idx]

	# Classifier: success prob with single-class fallback
	clf = LogisticRegression(max_iter=1000, solver="liblinear")
	try:
		# mypy: sklearn types ignored
		clf.fit(X_tr, y_s_tr)
		s_probs_tr = clf.predict_proba(X_tr)[:, 1]
		s_probs_va = clf.predict_proba(X_va)[:, 1]
	except Exception:
		# If only a single class present, fall back to a constant-prob model
		p_const = float(np.mean(y_s_tr)) if y_s_tr.size > 0 else 0.5
		s_probs_tr = np.full(shape=(X_tr.shape[0],), fill_value=p_const, dtype=float)
		s_probs_va = np.full(shape=(X_va.shape[0],), fill_value=p_const, dtype=float)
	auc_va = roc_auc_score(y_s_va, s_probs_va) if len(np.unique(y_s_va)) > 1 else 0.5
	ece_va = expected_calibration_error(s_probs_va, y_s_va, n_bins=10)
	brier_va = brier_score(s_probs_va, y_s_va)

	# Optional isotonic calibration on validation
	cal: IsotonicRegression | None = None
	try:
		if len(np.unique(y_s_va)) > 1:
			cal = IsotonicRegression(out_of_bounds="clip")
			cal.fit(s_probs_va, y_s_va)
			# Evaluate post-calibration ECE
			cal_probs = cal.predict(s_probs_va)
			ece_va = expected_calibration_error(cal_probs, y_s_va, n_bins=10)
			brier_va = brier_score(cal_probs, y_s_va)
		else:
			cal = None
	except Exception:
		cal = None

	# Regressor: risk
	reg = Ridge(alpha=1.0)
	reg.fit(X_tr, y_r_tr)
	r_pred_va = reg.predict(X_va)
	r_mse_va = float(np.mean((r_pred_va - y_r_va) ** 2))
	# Simple risk uncertainty: std of residuals on validation set
	risk_std = float(np.std(r_pred_va - y_r_va)) if r_pred_va.size > 1 else 0.0

	out_dir = Path(args.out)
	out_dir.mkdir(parents=True, exist_ok=True)
	# Save pack including optional calibration and risk_std
	try:
		import joblib  # type: ignore
		joblib.dump({"vec": vec, "clf": clf, "reg": reg, "cal": cal, "risk_std": risk_std}, out_dir / "wm_mlp.joblib")
		# Also maintain latest/best copies and registry
		import shutil
		from datetime import datetime
		(latest_path := out_dir / "latest.joblib").write_bytes((out_dir / "wm_mlp.joblib").read_bytes())
		# Load existing best metrics if any
		metrics_path = out_dir / "metrics.json"
		registry_path = out_dir / "registry.json"
		prev_best: Dict[str, float] = {}
		try:
			if registry_path.exists():
				reg_doc = json.loads(registry_path.read_text(encoding="utf-8"))
				prev_best = reg_doc.get("best", {}) if isinstance(reg_doc, dict) else {}
		except Exception:
			prev_best = {}
		# Save metrics next (computed below)
	except Exception:
		pass

	# Save metrics
	metrics = {
		"wm_auc": float(auc_va),
		"wm_ece": float(ece_va),
		"wm_brier": float(brier_va),
		"wm_risk_mse": float(r_mse_va),
		"n_examples": int(n),
		"val_ratio": float(args.val_ratio),
		"domain_feats": bool(args.domain_feats),
	}
	(out_dir / "metrics.json").write_text(json.dumps(metrics, ensure_ascii=False, indent=2), encoding="utf-8")

	# Update registry and best model
	try:
		reg_doc = {"history": [], "best": {}}
		if (out_dir / "registry.json").exists():
			try:
				reg_doc = json.loads((out_dir / "registry.json").read_text(encoding="utf-8"))
			except Exception:
				reg_doc = {"history": [], "best": {}}
		reg_doc.setdefault("history", []).append({"ts": __import__("datetime").datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), "metrics": metrics})
		best = reg_doc.get("best", {})
		def is_better(new: Dict[str, float], old: Dict[str, float]) -> bool:
			try:
				new_auc = float(new.get("wm_auc", 0.0))
				old_auc = float(old.get("wm_auc", 0.0))
				if new_auc != old_auc:
					return new_auc > old_auc
				# tie-breaker: lower ECE
				return float(new.get("wm_ece", 1e9)) < float(old.get("wm_ece", 1e9))
			except Exception:
				return True
		if (not best) or is_better(metrics, best):
			# Promote to best
			try:
				(out_dir / "best.joblib").write_bytes((out_dir / "wm_mlp.joblib").read_bytes())
			except Exception:
				pass
			reg_doc["best"] = metrics
		(out_dir / "registry.json").write_text(json.dumps(reg_doc, ensure_ascii=False, indent=2), encoding="utf-8")
	except Exception:
		pass

	# Write lightweight training metadata
	try:
		from datetime import datetime as _dt  # type: ignore
		meta = {
			"trained_at": _dt.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
			"val_ratio": float(args.val_ratio),
			"domain_feats": bool(args.domain_feats),
		}
		(out_dir / "metadata.json").write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")
	except Exception:
		pass

	print(json.dumps(metrics))
	return 0

if __name__ == "__main__":
	raise SystemExit(main())


		"wm_auc": float(auc_va),
		"wm_ece": float(ece_va),
		"wm_brier": float(brier_va),
		"wm_risk_mse": float(r_mse_va),
		"n_examples": int(n),
		"val_ratio": float(args.val_ratio),
		"domain_feats": bool(args.domain_feats),
	}
	(out_dir / "metrics.json").write_text(json.dumps(metrics, ensure_ascii=False, indent=2), encoding="utf-8")

	# Update registry and best model
	try:
		reg_doc = {"history": [], "best": {}}
		if (out_dir / "registry.json").exists():
			try:
				reg_doc = json.loads((out_dir / "registry.json").read_text(encoding="utf-8"))
			except Exception:
				reg_doc = {"history": [], "best": {}}
		reg_doc.setdefault("history", []).append({"ts": __import__("datetime").datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), "metrics": metrics})
		best = reg_doc.get("best", {})
		def is_better(new: Dict[str, float], old: Dict[str, float]) -> bool:
			try:
				new_auc = float(new.get("wm_auc", 0.0))
				old_auc = float(old.get("wm_auc", 0.0))
				if new_auc != old_auc:
					return new_auc > old_auc
				# tie-breaker: lower ECE
				return float(new.get("wm_ece", 1e9)) < float(old.get("wm_ece", 1e9))
			except Exception:
				return True
		if (not best) or is_better(metrics, best):
			# Promote to best
			try:
				(out_dir / "best.joblib").write_bytes((out_dir / "wm_mlp.joblib").read_bytes())
			except Exception:
				pass
			reg_doc["best"] = metrics
		(out_dir / "registry.json").write_text(json.dumps(reg_doc, ensure_ascii=False, indent=2), encoding="utf-8")
	except Exception:
		pass

	# Write lightweight training metadata
	try:
		from datetime import datetime as _dt  # type: ignore
		meta = {
			"trained_at": _dt.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
			"val_ratio": float(args.val_ratio),
			"domain_feats": bool(args.domain_feats),
		}
		(out_dir / "metadata.json").write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")


from __future__ import annotations
import logging

import argparse
import json
from pathlib import Path
from datetime import datetime, timezone

def main(argv: list[str] | None = None) -> int:
	parser = argparse.ArgumentParser(description="Stub trainer for heads")
	parser.add_argument("--head", required=True, choices=["plan", "patch", "cli", "policy", "hitl", "critic"])
	parser.add_argument("--sft_root", default="/data/agiattempt/agi_dw/data/sft")
	parser.add_argument("--out_dir", default="/data/agiattempt/agi_dw/artifacts/heads")
	args = parser.parse_args(argv)

	out_dir = Path(args.out_dir) / args.head / datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S")
	out_dir.mkdir(parents=True, exist_ok=True)
	# Write a tiny checkpoint metadata file
	(out_dir / "checkpoint.meta.json").write_text(json.dumps({
		"head": args.head,
		"ts": datetime.now(timezone.utc).isoformat(),
		"sft_root": args.sft_root,
		"note": "stub checkpoint",
	}, ensure_ascii=False, indent=2), encoding="utf-8")
	print(str(out_dir))
	return 0

if __name__ == "__main__":
	raise SystemExit(main())


import logging
import argparse
import json
import random
import time
from pathlib import Path
from subprocess import run, PIPE  # type: ignore
from typing import Any, Dict, List, Tuple
import tempfile
import math
import numpy as np  # type: ignore

def run_loop_cli(root: Path, task: str, planner_backend: str, model: str, timeout: int, planner_candidates: int, extra_args: List[str] | None = None) -> Tuple[bool, Dict[str, Any]]:
	cmd = [
		"python3", str(root / "scripts" / "run_loop_oscli.py"),
		"--planner-backend", planner_backend,
		"--verifier-backend", planner_backend,
		"--planner-model", model,
		"--verifier-model", model,
		"--timeout", str(timeout),
		"--task", task,
		"--planner-candidates", str(planner_candidates),
	]
	if extra_args:
		cmd.extend(list(extra_args))
	start = time.time()
	p = run(cmd, stdout=PIPE, stderr=PIPE, encoding="utf-8")
	dur = time.time() - start
	ok = False
	info: Dict[str, Any] = {"latency_sec": dur}
	try:
		last = p.stdout.strip().splitlines()[-1] if p.stdout else ""
		obj = json.loads(last) if last.startswith("{") else {}
		ok = str(obj.get("status", "")).lower() == "ok"
		info.update(obj)
	except Exception:
		ok = False
	return bool(ok), info

def main() -> int:
	ap = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	ap.add_argument("--domain", choices=["cli"], default="cli")
	ap.add_argument("--episodes", type=int, default=10)
	ap.add_argument("--planner-backend", choices=["hf"], default="hf")
	ap.add_argument("--model", default="meta-llama/Llama-3.2-3B")
	ap.add_argument("--timeout", type=int, default=20)
	ap.add_argument("--out", default=str(root / "data" / "planner_prefs"))
	# Preference optimization over candidate plans
	ap.add_argument("--pref-opt", action="store_true", help="Run preference optimization over planner candidate reranking weights")
	ap.add_argument("--planner-candidates", type=int, default=3)
	ap.add_argument("--use-tot", action="store_true")
	ap.add_argument("--with-wm", action="store_true", help="Enable WM prior and plan ranking during preference optimization")
	ap.add_argument("--weight-grid", default="0.3,0.5,0.7", help="Comma-separated grid for w_verifier and w_wm; w_self grid is 0.0,0.1,0.2")
	ap.add_argument("--planner-seeded", action="store_true", help="Use seeded candidate plans (no LLM) during evaluation")
	# New: GRPO-style group relative preference optimization over weight vectors
	ap.add_argument("--grpo", action="store_true", help="Use GRPO-style (group relative) update instead of PPO surrogate")
	args = ap.parse_args()

	out_dir = Path(args.out)
	out_dir.mkdir(parents=True, exist_ok=True)

	# If preference optimization is requested: grid-search weights for reranking
	if args.pref_opt:
		out_dir = Path(args.out)
		out_dir.mkdir(parents=True, exist_ok=True)
		grid_vals = []
		try:
			grid_vals = [float(x) for x in str(args.weight_grid).split(",") if x.strip()]
		except Exception:
			grid_vals = [0.3, 0.5, 0.7]
		self_grid = [0.0, 0.1, 0.2]
		tasks = ["count_lines", "grep_error"]
		best = {"score": -1.0, "w": {"w_verifier": 0.5, "w_wm": 0.5, "w_self": 0.1}}
		for wv in grid_vals:
			for ww in grid_vals:
				for ws in self_grid:
					weights = {"w_verifier": float(wv), "w_wm": float(ww), "w_self": float(ws)}
					with tempfile.NamedTemporaryFile("w", delete=False, suffix=".json") as tf:
						tf.write(json.dumps(weights))
						wpath = tf.name
					# Evaluate across tasks (one run each for speed)
					succ = 0
					runs = 0
					for t in tasks:
						extra = ["--wm-plan-rank", "--planner-pref-weights", wpath]
						# Always forward planner-seeded when requested, independent of ToT usage
						if args.planner_seeded:
							extra.append("--planner-seeded")
						if args.use_tot:
							extra.append("--planner-tot")
						if args.with_wm:
							extra.insert(0, "--wm-prior")
						ok, _ = run_loop_cli(root, t, args.planner_backend, args.model, int(args.timeout), planner_candidates=int(args.planner_candidates), extra_args=extra)
						succ += 1 if ok else 0
						runs += 1
					sr = float(succ) / max(1.0, float(runs))
					if sr > best["score"]:
						best = {"score": sr, "w": weights}
		# Save best weights pack
		(out_dir / "pref_weights.json").write_text(json.dumps(best["w"], ensure_ascii=False, indent=2), encoding="utf-8")
		# Also write a metrics file for dashboard/CI compatibility
		(out_dir / "metrics.json").write_text(json.dumps({"mode": "pref_opt", "best_success_rate": float(best["score"]), "best_weights": best["w"]}, ensure_ascii=False, indent=2), encoding="utf-8")
		print(json.dumps({"ok": True, "mode": "pref_opt", "success_rate": best["score"], "weights": best["w"]}))
		return 0

	# PPO/GRPO over preference reranker weights (w_verifier, w_wm, w_self)
	tasks = ["count_lines", "grep_error"]

	def evaluate_weights(weights: Dict[str, float]) -> float:
		"""Return success rate across a small eval roll.
		Writes weights to a temp file and runs loops with planner reranking and options.
		"""
		w = {"w_verifier": float(weights.get("w_verifier", 0.5)), "w_wm": float(weights.get("w_wm", 0.5)), "w_self": float(weights.get("w_self", 0.1))}
		with tempfile.NamedTemporaryFile("w", delete=False, suffix=".json") as tf:
			tf.write(json.dumps(w))
			wpath = tf.name
		succ = 0
		runs = 0
		for t in tasks:
		extra: List[str] = ["--wm-plan-rank", "--planner-pref-weights", wpath]
			# Always forward planner-seeded when requested, independent of ToT usage
			if args.planner_seeded:
				extra.append("--planner-seeded")
			if args.use_tot:
				extra.append("--planner-tot")
			if args.with_wm:
				extra.insert(0, "--wm-prior")
			ok, _ = run_loop_cli(
				root,
				t,
				args.planner_backend,
				args.model,
				int(args.timeout),
				planner_candidates=int(args.planner_candidates),
				extra_args=extra,
			)
			succ += 1 if ok else 0
			runs += 1
		return float(succ) / max(1.0, float(runs))

	# Policy: independent Gaussians over 3 weights clipped to [0,1]
	mu = np.asarray([0.5, 0.5, 0.1], dtype=float)
	sigma = np.asarray([0.20, 0.20, 0.10], dtype=float)
	lr = 0.10
	epsilon = 0.20
	entropy_coef = 0.00  # fixed sigma → entropy constant; keep zero
	samples_per_iter = 4
	iters = max(1, int(args.episodes))
	best = {"score": -1.0, "w": {"w_verifier": 0.5, "w_wm": 0.5, "w_self": 0.1}}
	baseline = 0.0

	# Evaluate baseline (default weights) to measure improvement
	def _eval_baseline() -> float:
		try:
			return evaluate_weights({"w_verifier": 0.5, "w_wm": 0.5, "w_self": 0.1})
		except Exception:
			return 0.0

	baseline_sr = _eval_baseline()

	for _ in range(iters):
		mu_old = mu.copy()
		rews: List[float] = []
		samps: List[np.ndarray] = []
		logp_old: List[float] = []
		# Sample candidate weight vectors
		for _j in range(samples_per_iter):
			x = np.random.normal(loc=mu, scale=sigma)
			samps.append(x)
			# Log prob under old policy
			lp = -0.5 * np.sum(((x - mu_old) / sigma) ** 2) - np.sum(np.log(sigma * math.sqrt(2 * math.pi)))
			logp_old.append(float(lp))
			# Evaluate with clipped weights
			wx = {"w_verifier": float(np.clip(x[0], 0.0, 1.0)), "w_wm": float(np.clip(x[1], 0.0, 1.0)), "w_self": float(np.clip(x[2], 0.0, 1.0))}
			rew = evaluate_weights(wx)
			rews.append(float(rew))
			if rew > best["score"]:
				best = {"score": float(rew), "w": wx}
		# Compute group-relative advantages (GRPO) or PPO-style normalized advantages
		rews_np = np.asarray(rews, dtype=float)
		if bool(args.grpo):
			# Rank-based advantages within group to emphasize preferences
			order = np.argsort(rews_np)
			ranks = np.empty_like(order, dtype=float)
			ranks[order] = np.arange(1, len(rews_np) + 1)
			adv = (ranks - ranks.mean()) / (ranks.std() + 1e-8)
		else:
			adv = rews_np - (baseline if baseline > 0 else rews_np.mean())
			if float(adv.std()) > 1e-8:
				adv = (adv - adv.mean()) / (adv.std() + 1e-8)
		# Update baseline (moving average of mean reward)
		baseline = 0.9 * baseline + 0.1 * float(rews_np.mean())
		# Compute gradients wrt mu (approximate PPO; with GRPO we still use clipped ratios for stability)
		grad = np.zeros_like(mu)
		for x, a, lp_old in zip(samps, adv, logp_old):
			lp_new = -0.5 * np.sum(((x - mu) / sigma) ** 2) - np.sum(np.log(sigma * math.sqrt(2 * math.pi)))
			ratio = math.exp(float(lp_new - lp_old))
			ratio_clip = max(min(ratio, 1.0 + epsilon), 1.0 - epsilon)
			coeff = ratio_clip if bool(args.grpo) else (ratio if abs(ratio * float(a)) <= abs(ratio_clip * float(a)) else ratio_clip)
			# d/dmu logp_new = (x - mu)/sigma^2
			dlog = (x - mu) / (sigma ** 2)
			g_i = coeff * float(a) * dlog
			grad += g_i
		# Gradient ascent on surrogate objective
		mu = mu + lr * (grad / float(samples_per_iter))
		mu = np.clip(mu, 0.0, 1.0)

	# Save best weights and a small metrics file
	(out_dir / "planner_ppo.json").write_text(json.dumps(best["w"], ensure_ascii=False, indent=2), encoding="utf-8")
	mode = "grpo" if bool(args.grpo) else "ppo"
	metrics = {
		"mode": mode,
		"iters": iters,
		"baseline_success_rate": float(baseline_sr),
		"best_success_rate": float(best["score"]),
		"improvement": float(best["score"] - baseline_sr),
		"best_weights": best["w"],
	}
	(out_dir / "metrics.json").write_text(json.dumps(metrics, ensure_ascii=False, indent=2), encoding="utf-8")
	print(json.dumps({"ok": True, **metrics, "weights": best["w"]}))
	return 0

if __name__ == "__main__":
	raise SystemExit(main())


from __future__ import annotations

import logging
import argparse
import json
import shlex
import subprocess
import os
from pathlib import Path
from typing import Dict, Any, List

def run_cmd(cmd: str, cwd: Path, extra_env: Dict[str, str] | None = None) -> str:
    try:
        parts = shlex.split(cmd)
        env = os.environ.copy()
        if extra_env:
            env.update(extra_env)
        out = subprocess.check_output(parts, cwd=str(cwd), stderr=subprocess.STDOUT, timeout=60, env=env)
        return out.decode("utf-8", errors="ignore")
    except subprocess.CalledProcessError as e:
        return (e.output or b"").decode("utf-8", errors="ignore")
    except Exception as e:
        return f"ERROR: {e}"

def main() -> int:
    ap = argparse.ArgumentParser(description="Validate bench infra refactor tasks by running their verify commands")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--in", dest="inp", default=str(root / "data" / "traces" / "benchinfra_tasks.jsonl"))
    ap.add_argument("--out", default=str(root / "data" / "sandbox" / "tmp" / "benchinfra_validation.json"))
    args = ap.parse_args()

    inp = Path(args.inp)
    results: List[Dict[str, Any]] = []
    if not inp.exists():
        out = {"ok": False, "error": f"missing input: {inp}"}
        Path(args.out).parent.mkdir(parents=True, exist_ok=True)
        Path(args.out).write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
        print(json.dumps(out))
        return 2

    for line in inp.read_text(encoding="utf-8").splitlines():
        s = line.strip()
        if not s:
            continue
        try:
            ex = json.loads(s)
        except Exception:
            continue
        verify = ex.get("verify") or {}
        cmd = str(verify.get("cmd") or "").strip()
        must = list(verify.get("must_contain") or [])
        if not cmd:
            results.append({"id": ex.get("id"), "ok": False, "reason": "no verify cmd"})
            continue
        # Ensure PYTHONPATH includes repo workspace parent (e.g., /data/agiattempt)
        py_path = str(root.parent)
        out = run_cmd(cmd, root, {"PYTHONPATH": py_path})
        ok = all(m in out for m in must) if must else (out is not None)
        # Fallback: search in declared files if command output suppressed (e.g., '@' in Make recipes)
        if not ok and ex.get("files"):
            try:
                corpus = "\n".join(
                    (root / str(p)).read_text(encoding="utf-8", errors="ignore")
                    for p in ex.get("files")
                    if (root / str(p)).exists()
                )
                ok = all(m in corpus for m in must)
            except Exception:
                ok = False
        results.append({"id": ex.get("id"), "ok": bool(ok), "cmd": cmd, "must": must})

    overall = all(r.get("ok") for r in results) if results else False
    payload = {"ok": overall, "results": results}

    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)
    outp.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps(payload))
    return 0 if overall else 2

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations

import logging
import argparse
import json
import shlex
import subprocess
import os
from pathlib import Path
from typing import Dict, Any, List

def run_cmd(cmd: str, cwd: Path, extra_env: Dict[str, str] | None = None) -> str:
    try:
        parts = shlex.split(cmd)
        env = os.environ.copy()
        if extra_env:
            env.update(extra_env)
        out = subprocess.check_output(parts, cwd=str(cwd), stderr=subprocess.STDOUT, timeout=60, env=env)
        return out.decode("utf-8", errors="ignore")
    except subprocess.CalledProcessError as e:
        return (e.output or b"").decode("utf-8", errors="ignore")
    except Exception as e:
        return f"ERROR: {e}"

def main() -> int:
    ap = argparse.ArgumentParser(description="Validate bench infra refactor tasks by running their verify commands")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--in", dest="inp", default=str(root / "data" / "traces" / "benchinfra_tasks.jsonl"))
    ap.add_argument("--out", default=str(root / "data" / "sandbox" / "tmp" / "benchinfra_validation.json"))
    args = ap.parse_args()

    inp = Path(args.inp)
    results: List[Dict[str, Any]] = []
    if not inp.exists():
        out = {"ok": False, "error": f"missing input: {inp}"}
        Path(args.out).parent.mkdir(parents=True, exist_ok=True)
        Path(args.out).write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
        print(json.dumps(out))
        return 2

    for line in inp.read_text(encoding="utf-8").splitlines():
        s = line.strip()


import subprocess
import os
from pathlib import Path
from typing import Dict, Any, List

def run_cmd(cmd: str, cwd: Path, extra_env: Dict[str, str] | None = None) -> str:
    try:
        parts = shlex.split(cmd)
        env = os.environ.copy()
        if extra_env:
            env.update(extra_env)
        out = subprocess.check_output(parts, cwd=str(cwd), stderr=subprocess.STDOUT, timeout=60, env=env)
        return out.decode("utf-8", errors="ignore")
    except subprocess.CalledProcessError as e:
        return (e.output or b"").decode("utf-8", errors="ignore")
    except Exception as e:
        return f"ERROR: {e}"

def main() -> int:
    ap = argparse.ArgumentParser(description="Validate bench infra refactor tasks by running their verify commands")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--in", dest="inp", default=str(root / "data" / "traces" / "benchinfra_tasks.jsonl"))
    ap.add_argument("--out", default=str(root / "data" / "sandbox" / "tmp" / "benchinfra_validation.json"))
    args = ap.parse_args()

    inp = Path(args.inp)
    results: List[Dict[str, Any]] = []
    if not inp.exists():
        out = {"ok": False, "error": f"missing input: {inp}"}
        Path(args.out).parent.mkdir(parents=True, exist_ok=True)
        Path(args.out).write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
        print(json.dumps(out))
        return 2

    for line in inp.read_text(encoding="utf-8").splitlines():
        s = line.strip()
        if not s:
            continue
        try:
            ex = json.loads(s)
        except Exception:
            continue
        verify = ex.get("verify") or {}
        cmd = str(verify.get("cmd") or "").strip()
        must = list(verify.get("must_contain") or [])
        if not cmd:
            results.append({"id": ex.get("id"), "ok": False, "reason": "no verify cmd"})
            continue
        # Ensure PYTHONPATH includes repo workspace parent (e.g., /data/agiattempt)
        py_path = str(root.parent)
        out = run_cmd(cmd, root, {"PYTHONPATH": py_path})
        ok = all(m in out for m in must) if must else (out is not None)
        # Fallback: search in declared files if command output suppressed (e.g., '@' in Make recipes)
        if not ok and ex.get("files"):
            try:
                corpus = "\n".join(
                    (root / str(p)).read_text(encoding="utf-8", errors="ignore")
                    for p in ex.get("files")
                    if (root / str(p)).exists()
                )
                ok = all(m in corpus for m in must)
            except Exception:
                ok = False
        results.append({"id": ex.get("id"), "ok": bool(ok), "cmd": cmd, "must": must})

    overall = all(r.get("ok") for r in results) if results else False
    payload = {"ok": overall, "results": results}

    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)
    outp.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps(payload))
    return 0 if overall else 2

if __name__ == "__main__":
    raise SystemExit(main())


import torch
import torch.nn.functional as F
from torch.nn.utils import clip_grad_norm_
from transformers import AutoModelForCausalLM, AutoTokenizer

from agi_dw.core.metaopt.group_utils import make_param_groups
from agi_dw.core.metaopt.gate_grouped import GateNetGrouped
from agi_dw.core.metaopt.metaopt_grouped import MixtureMetaOptGrouped

def hf_batchify(tokenizer, texts, block_size=1024, device="cuda"):
    ids = torch.tensor(tokenizer.encode("".join(texts)), dtype=torch.long, device=device)

    def next_batch(batch_size):
        ix = torch.randint(0, ids.size(0) - block_size - 1, (batch_size,), device=device)
        x = torch.stack([ids[i : i + block_size] for i in ix])
        y = torch.stack([ids[i + 1 : i + block_size + 1] for i in ix])
        return x, y

    return next_batch

def main():
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model = AutoModelForCausalLM.from_pretrained("gpt2").to(device)
    tok = AutoTokenizer.from_pretrained("gpt2")
    tok.pad_token = tok.eos_token

    train_next = hf_batchify(tok, ["The optimizer is an evolving policy. "] * 500, block_size=256, device=device)
    val_next = hf_batchify(tok, ["Validation stream. "] * 50, block_size=256, device=device)

    groups = make_param_groups(model)
    gate = GateNetGrouped(n_groups=len(groups), hidden=128)
    meta = MixtureMetaOptGrouped(model, groups, gate, base_lr=3e-4, base_wd=0.01, device=device)

    for step in range(2000):
        x, y = train_next(2)
        out = model(input_ids=x, labels=y)
        loss = out.loss

        for p in model.parameters():
            if p.grad is not None:
                p.grad.zero_()
        loss.backward()
        clip_grad_norm_(model.parameters(), 1.0)

        meta.step(float(loss.item()))

        if step % 200 == 0:
            vx, vy = val_next(4)
            with torch.no_grad():
                vloss = model(input_ids=vx, labels=vy).loss.item()
            for gi in range(len(groups)):
                meta.bandit_reward(gi, -vloss)
            print(f"[{step}] train_loss={loss.item():.3f} val_loss={vloss:.3f}")

if __name__ == "__main__":
    main()


import torch
import torch.nn.functional as F
from torch.nn.utils import clip_grad_norm_
from transformers import AutoModelForCausalLM, AutoTokenizer

from agi_dw.core.metaopt.group_utils import make_param_groups
from agi_dw.core.metaopt.gate_grouped import GateNetGrouped
from agi_dw.core.metaopt.metaopt_grouped import MixtureMetaOptGrouped

def hf_batchify(tokenizer, texts, block_size=1024, device="cuda"):
    ids = torch.tensor(tokenizer.encode("".join(texts)), dtype=torch.long, device=device)

    def next_batch(batch_size):
        ix = torch.randint(0, ids.size(0) - block_size - 1, (batch_size,), device=device)
        x = torch.stack([ids[i : i + block_size] for i in ix])
        y = torch.stack([ids[i + 1 : i + block_size + 1] for i in ix])
        return x, y

    return next_batch

def main():
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model = AutoModelForCausalLM.from_pretrained("gpt2").to(device)
    tok = AutoTokenizer.from_pretrained("gpt2")
    tok.pad_token = tok.eos_token

    train_next = hf_batchify(tok, ["The optimizer is an evolving policy. "] * 500, block_size=256, device=device)
    val_next = hf_batchify(tok, ["Validation stream. "] * 50, block_size=256, device=device)

    groups = make_param_groups(model)
    gate = GateNetGrouped(n_groups=len(groups), hidden=128)
    meta = MixtureMetaOptGrouped(model, groups, gate, base_lr=3e-4, base_wd=0.01, device=device)

    for step in range(2000):
        x, y = train_next(2)
        out = model(input_ids=x, labels=y)
        loss = out.loss


from torch.nn.utils import clip_grad_norm_
from transformers import AutoModelForCausalLM, AutoTokenizer

from agi_dw.core.metaopt.group_utils import make_param_groups
from agi_dw.core.metaopt.gate_grouped import GateNetGrouped
from agi_dw.core.metaopt.metaopt_grouped import MixtureMetaOptGrouped

def hf_batchify(tokenizer, texts, block_size=1024, device="cuda"):
    ids = torch.tensor(tokenizer.encode("".join(texts)), dtype=torch.long, device=device)

    def next_batch(batch_size):
        ix = torch.randint(0, ids.size(0) - block_size - 1, (batch_size,), device=device)
        x = torch.stack([ids[i : i + block_size] for i in ix])
        y = torch.stack([ids[i + 1 : i + block_size + 1] for i in ix])
        return x, y

    return next_batch

def main():
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model = AutoModelForCausalLM.from_pretrained("gpt2").to(device)
    tok = AutoTokenizer.from_pretrained("gpt2")
    tok.pad_token = tok.eos_token

    train_next = hf_batchify(tok, ["The optimizer is an evolving policy. "] * 500, block_size=256, device=device)
    val_next = hf_batchify(tok, ["Validation stream. "] * 50, block_size=256, device=device)

    groups = make_param_groups(model)
    gate = GateNetGrouped(n_groups=len(groups), hidden=128)
    meta = MixtureMetaOptGrouped(model, groups, gate, base_lr=3e-4, base_wd=0.01, device=device)

    for step in range(2000):
        x, y = train_next(2)
        out = model(input_ids=x, labels=y)
        loss = out.loss

        for p in model.parameters():
            if p.grad is not None:
                p.grad.zero_()
        loss.backward()
        clip_grad_norm_(model.parameters(), 1.0)

        meta.step(float(loss.item()))

        if step % 200 == 0:
            vx, vy = val_next(4)
            with torch.no_grad():
                vloss = model(input_ids=vx, labels=vy).loss.item()
            for gi in range(len(groups)):
                meta.bandit_reward(gi, -vloss)
            print(f"[{step}] train_loss={loss.item():.3f} val_loss={vloss:.3f}")

if __name__ == "__main__":
    main()


import torch
import torch.nn.functional as F
from torch.nn.utils import clip_grad_norm_
from transformers import AutoModelForCausalLM, AutoTokenizer

from agi_dw.core.metaopt.group_utils import make_param_groups
from agi_dw.core.metaopt.gate_grouped import GateNetGrouped
from agi_dw.core.metaopt.metaopt_grouped import MixtureMetaOptGrouped

def hf_batchify(tokenizer, texts, block_size=1024, device="cuda"):
    ids = torch.tensor(tokenizer.encode("".join(texts)), dtype=torch.long, device=device)

    def next_batch(batch_size):
        ix = torch.randint(0, ids.size(0) - block_size - 1, (batch_size,), device=device)
        x = torch.stack([ids[i : i + block_size] for i in ix])
        y = torch.stack([ids[i + 1 : i + block_size + 1] for i in ix])
        return x, y

    return next_batch

def main():
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model = AutoModelForCausalLM.from_pretrained("gpt2").to(device)
    tok = AutoTokenizer.from_pretrained("gpt2")
    tok.pad_token = tok.eos_token

    train_next = hf_batchify(tok, ["The optimizer is an evolving policy. "] * 500, block_size=256, device=device)
    val_next = hf_batchify(tok, ["Validation stream. "] * 50, block_size=256, device=device)

    groups = make_param_groups(model)
    gate = GateNetGrouped(n_groups=len(groups), hidden=128)
    meta = MixtureMetaOptGrouped(model, groups, gate, base_lr=3e-4, base_wd=0.01, device=device)

    for step in range(2000):
        x, y = train_next(2)
        out = model(input_ids=x, labels=y)


from __future__ import annotations
import logging

import json
from pathlib import Path

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "trace_harvest.json"
    out = {
        "ok": True,
        "harvested": 0,
        "notes": "stub: collect traces from CI runs into datasets",
        "out": str(out_path),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations

import json
from pathlib import Path
from typing import Any, Dict

def scan_presence(root: Path) -> Dict[str, bool]:
    logging_found = False
    metrics_found = False
    traces_found = False
    for p in root.rglob("*.py"):
        try:
            text = p.read_text(encoding="utf-8", errors="ignore")
        except Exception:
            continue
        s = text
        if ("import logging" in s) or ("getLogger(" in s):
            logging_found = True
        if ("prometheus_client" in s) or ("statsd" in s) or ("meter." in s):
            metrics_found = True
        if ("opentelemetry" in s) or ("trace.get_tracer(" in s):
            traces_found = True
    return {"logging": logging_found, "metrics": metrics_found, "traces": traces_found}

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "obs_check.json"
    presence = scan_presence(root)
    out = {
        "ok": any(presence.values()),
        "presence": presence,
        "out": str(out_path),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


def scan_presence(root: Path) -> Dict[str, bool]:
    logging_found = False
    metrics_found = False
    traces_found = False
    for p in root.rglob("*.py"):
        try:
            text = p.read_text(encoding="utf-8", errors="ignore")
        except Exception:
            continue
        s = text
        if ("import logging" in s) or ("getLogger(" in s):
            logging_found = True
        if ("prometheus_client" in s) or ("statsd" in s) or ("meter." in s):
            metrics_found = True
        if ("opentelemetry" in s) or ("trace.get_tracer(" in s):
            traces_found = True
    return {"logging": logging_found, "metrics": metrics_found, "traces": traces_found}

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "obs_check.json"
    presence = scan_presence(root)
    out = {
        "ok": any(presence.values()),
        "presence": presence,
        "out": str(out_path),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "plan_api_diff.json"
    out = {
        "ok": True,
        "api_diff": {
            "breaking": [],
            "additive": [],
            "deprecations": [],
        },
        "notes": "stub: diff OpenAPI/GraphQL/Proto when present",
        "out": str(out_path),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "pattern_update.json"
    out = {
        "ok": True,
        "patterns": [],
        "notes": "stub: update pattern catalog entries from lessons learned",
        "out": str(out_path),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "error_budget.json"
    out = {
        "ok": True,
        "error_budget": {"burn": 0.0},
        "notes": "stub: compute error budget burn from SLO data",
        "out": str(out_path),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path
import shutil
import subprocess

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "sbom.json"
    out = {"ok": True, "sbom": {"manifests": ["requirements.txt", "package.json", "uv.lock"]}, "out": str(out_path)}
    # Prefer CycloneDX if available
    try:
        if shutil.which("cyclonedx-py"):
            # Generate for Python requirements
            sbom_txt = subprocess.check_output(["cyclonedx-py", "--format", "json"], stderr=subprocess.STDOUT).decode("utf-8", errors="ignore")
            out["cyclonedx"] = True
            out_path.write_text(sbom_txt, encoding="utf-8")
            print(json.dumps({"ok": True, "out": str(out_path)}))
            return 0
    except Exception:
        pass
    # Fallback minimal stub
    out["notes"] = "stub: install cyclonedx/sbom tools for richer output"
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Any, Dict, List

def load_json(path: Path) -> Dict[str, Any]:
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    idx_path = root / "data" / "sandbox" / "tmp" / "index.json"
    out_path = root / "data" / "sandbox" / "tmp" / "plan_risk.json"
    policies_path = root / "data" / "sandbox" / "config" / "policies.yaml"

    # Load or build index
    idx = load_json(idx_path)
    if not idx:
        try:
            from agi_dw.tools.code_index import build_index  # type: ignore
            idx = build_index(root)
            idx_path.parent.mkdir(parents=True, exist_ok=True)
            idx_path.write_text(json.dumps(idx, ensure_ascii=False, indent=2), encoding="utf-8")
        except Exception:
            idx = {}

    # Load policies (YAML if available, else JSON)
    def _safe_load_yaml(path: Path) -> Dict[str, Any]:
        if not path.exists():
            return {}
        try:
            import yaml  # type: ignore
            data = yaml.safe_load(path.read_text(encoding="utf-8"))
            return data if isinstance(data, dict) else {}
        except Exception:
            try:
                return json.loads(path.read_text(encoding="utf-8"))
            except Exception:
                return {}

    policies = _safe_load_yaml(policies_path)

    # Compute deterministic repo risk using integrated policy module
    try:
        from agi_dw.core.hitl.policy import compute_repo_risk  # type: ignore
        payload = compute_repo_risk(idx, policies=policies, repo_root=root)
    except Exception:
        payload = {"score": 0.0, "components": {}, "violations": [], "caps": {}, "ok": True}

    out = {
        "ok": bool(payload.get("ok", True)),
        "score": float(payload.get("score", 0.0) or 0.0),
        "components": payload.get("components", {}),
        "violations": payload.get("violations", []),
        "caps": payload.get("caps", {}),
        "sources": {"index": str(idx_path), "policies": str(policies_path)},
        "out": str(out_path),
    }

    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": bool(out.get("ok", True)), "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Any, Dict, List

def load_json(path: Path) -> Dict[str, Any]:
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    idx_path = root / "data" / "sandbox" / "tmp" / "index.json"
    out_path = root / "data" / "sandbox" / "tmp" / "plan_risk.json"
    policies_path = root / "data" / "sandbox" / "config" / "policies.yaml"

    # Load or build index
    idx = load_json(idx_path)
    if not idx:
        try:
            from agi_dw.tools.code_index import build_index  # type: ignore
            idx = build_index(root)
            idx_path.parent.mkdir(parents=True, exist_ok=True)
            idx_path.write_text(json.dumps(idx, ensure_ascii=False, indent=2), encoding="utf-8")
        except Exception:
            idx = {}

    # Load policies (YAML if available, else JSON)


def main() -> int:
    root = Path(__file__).resolve().parents[2]
    idx_path = root / "data" / "sandbox" / "tmp" / "index.json"
    out_path = root / "data" / "sandbox" / "tmp" / "plan_risk.json"
    policies_path = root / "data" / "sandbox" / "config" / "policies.yaml"

    # Load or build index
    idx = load_json(idx_path)
    if not idx:
        try:
            from agi_dw.tools.code_index import build_index  # type: ignore
            idx = build_index(root)
            idx_path.parent.mkdir(parents=True, exist_ok=True)
            idx_path.write_text(json.dumps(idx, ensure_ascii=False, indent=2), encoding="utf-8")
        except Exception:
            idx = {}

    # Load policies (YAML if available, else JSON)
    def _safe_load_yaml(path: Path) -> Dict[str, Any]:
        if not path.exists():
            return {}
        try:
            import yaml  # type: ignore
            data = yaml.safe_load(path.read_text(encoding="utf-8"))
            return data if isinstance(data, dict) else {}
        except Exception:
            try:
                return json.loads(path.read_text(encoding="utf-8"))
            except Exception:
                return {}

    policies = _safe_load_yaml(policies_path)

    # Compute deterministic repo risk using integrated policy module
    try:
        from agi_dw.core.hitl.policy import compute_repo_risk  # type: ignore
        payload = compute_repo_risk(idx, policies=policies, repo_root=root)
    except Exception:
        payload = {"score": 0.0, "components": {}, "violations": [], "caps": {}, "ok": True}

    out = {
        "ok": bool(payload.get("ok", True)),
        "score": float(payload.get("score", 0.0) or 0.0),
        "components": payload.get("components", {}),
        "violations": payload.get("violations", []),
        "caps": payload.get("caps", {}),
        "sources": {"index": str(idx_path), "policies": str(policies_path)},
        "out": str(out_path),
    }


from __future__ import annotations
import logging

import json
from pathlib import Path
import shutil
import subprocess

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "deps_audit.json"
    out = {"ok": True, "findings": [], "out": str(out_path)}
    # Prefer pip-audit if available
    try:
        if shutil.which("pip-audit"):
            res = subprocess.run(["pip-audit", "-f", "json"], capture_output=True, text=True)
            if res.returncode == 0 and res.stdout:
                data = json.loads(res.stdout)
                # Standardize findings
                for item in data if isinstance(data, list) else []:
                    name = item.get("name")
                    for v in item.get("vulns", []) or []:
                        out["findings"].append({"pkg": name, "id": v.get("id"), "severity": v.get("severity", "unknown")})
                # ok=false if any high
                out["ok"] = not any((f.get("severity", "").lower() == "high") for f in out["findings"])
                out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
                print(json.dumps({"ok": bool(out.get("ok", True)), "out": str(out_path)}))
                return 0
    except Exception:
        pass
    # Fallback stub
    out["notes"] = "stub: install pip-audit for real results"
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "trace_dataset_update.json"
    out = {
        "ok": True,
        "updated": True,
        "notes": "stub: merge new traces into SFT datasets",
        "out": str(out_path),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "deps_upgrade.json"
    out = {
        "ok": True,
        "planned": [],
        "notes": "stub: propose safe upgrades and open PRs",
        "out": str(out_path),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Any, Dict

def _safe_load_yaml(path: Path) -> Dict[str, Any]:
    if not path.exists():
        return {}
    try:
        import yaml  # type: ignore
        data = yaml.safe_load(path.read_text(encoding="utf-8"))
        return data if isinstance(data, dict) else {}
    except Exception:
        return {}

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "slo_report.json"
    policies_path = root / "data" / "sandbox" / "config" / "policies.yaml"
    telemetry_dir = root / "data" / "telemetry"

    # Minimal telemetry aggregation: read jsonl or json files
    latency_samples = []
    availability = 1.0
    if telemetry_dir.exists():
        for p in telemetry_dir.glob("*.json*"):
            try:
                txt = p.read_text(encoding="utf-8", errors="ignore")
                for ln in txt.splitlines():
                    ln = ln.strip()
                    if not ln:
                        continue
                    obj = None
                    try:
                        obj = json.loads(ln)
                    except Exception:
                        obj = json.loads(txt)
                    if not isinstance(obj, dict):
                        continue
                    if "latency_ms" in obj:
                        latency_samples.append(float(obj.get("latency_ms", 0)))
                    if "availability" in obj:
                        availability = float(obj.get("availability", availability))
            except Exception:
                continue
    latency_samples.sort()
    p90 = latency_samples[int(0.9 * (len(latency_samples) - 1))] if latency_samples else 0.0

    policies = _safe_load_yaml(policies_path)
    slo_budget = (policies.get("slo") or {}) if isinstance(policies, dict) else {}
    max_p90 = float(slo_budget.get("latency_p90_ms", 1000))
    min_avail = float(slo_budget.get("availability", 0.999))

    out = {
        "ok": bool((availability >= min_avail) and (p90 <= max_p90)),
        "slo": {"availability": round(availability, 6), "latency_p90": round(float(p90), 3), "budgets": {"availability": min_avail, "latency_p90_ms": max_p90}},
        "notes": "computed from telemetry dir if present; budgets from policies.yaml",
        "out": str(out_path),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Any, Dict

def _safe_load_yaml(path: Path) -> Dict[str, Any]:
    if not path.exists():
        return {}
    try:
        import yaml  # type: ignore
        data = yaml.safe_load(path.read_text(encoding="utf-8"))
        return data if isinstance(data, dict) else {}
    except Exception:
        return {}

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    out_path = root / "data" / "sandbox" / "tmp" / "slo_report.json"
    policies_path = root / "data" / "sandbox" / "config" / "policies.yaml"
    telemetry_dir = root / "data" / "telemetry"

    # Minimal telemetry aggregation: read jsonl or json files
    latency_samples = []
    availability = 1.0
    if telemetry_dir.exists():
        for p in telemetry_dir.glob("*.json*"):
            try:
                txt = p.read_text(encoding="utf-8", errors="ignore")
                for ln in txt.splitlines():
                    ln = ln.strip()
                    if not ln:
                        continue
                    obj = None


from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Any, Dict, List, Tuple
import fnmatch

def load_json(path: Path) -> Dict[str, Any]:
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}

def load_codeowners(path: Path) -> List[Tuple[str, List[str]]]:
    rules: List[Tuple[str, List[str]]] = []
    if not path.exists():
        return rules
    for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
        s = line.strip()
        if not s or s.startswith("#"):
            continue
        parts = s.split()
        if len(parts) >= 2:
            pattern = parts[0]
            owners = parts[1:]
            rules.append((pattern, owners))
    return rules

def match_owners(rel_path: str, rules: List[Tuple[str, List[str]]]) -> List[str]:
    owners: List[str] = []
    for pattern, pats_owners in rules:
        try:
            if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch("/" + rel_path, pattern):
                owners.extend(pats_owners)
        except Exception:
            continue
    # dedupe preserving order
    seen: set[str] = set()
    out: List[str] = []
    for o in owners:
        if o not in seen:
            seen.add(o)
            out.append(o)
    return out

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    idx_path = root / "data" / "sandbox" / "tmp" / "index.json"
    codeowners_path = root / "CODEOWNERS"
    out_path = root / "data" / "sandbox" / "tmp" / "plan_impact.json"

    idx = load_json(idx_path)
    entries: List[Dict[str, Any]] = idx.get("entries", []) or []
    rules = load_codeowners(codeowners_path)

    files_impact: List[Dict[str, Any]] = []
    for it in entries:
        file_abs = str(it.get("file", ""))
        if not file_abs:
            continue
        try:
            rel = str(Path(file_abs).resolve().relative_to(root))
        except Exception:
            rel = file_abs
        defs = it.get("defs", []) or []
        owners = match_owners(rel, rules)
        files_impact.append(
            {
                "path": rel,
                "symbols": len(defs),
                "owners": owners,
            }
        )

    # Aggregate modules (top-N by symbol count)
    top = sorted(files_impact, key=lambda x: int(x.get("symbols", 0)), reverse=True)[:50]
    owners_all: List[str] = []
    for x in top:
        for o in x.get("owners", []) or []:
            if o not in owners_all:
                owners_all.append(o)

    out = {
        "ok": True,
        "impact": {
            "top_modules": top,
            "owners": owners_all,
        },
        "sources": {"index": str(idx_path), "codeowners": str(codeowners_path)},
        "out": str(out_path),
    }

    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Any, Dict, List, Tuple
import fnmatch

def load_json(path: Path) -> Dict[str, Any]:
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}

def load_codeowners(path: Path) -> List[Tuple[str, List[str]]]:
    rules: List[Tuple[str, List[str]]] = []
    if not path.exists():
        return rules
    for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
        s = line.strip()
        if not s or s.startswith("#"):
            continue
        parts = s.split()
        if len(parts) >= 2:
            pattern = parts[0]
            owners = parts[1:]
            rules.append((pattern, owners))
    return rules

def match_owners(rel_path: str, rules: List[Tuple[str, List[str]]]) -> List[str]:
    owners: List[str] = []


from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Any, Dict, List, Tuple
import fnmatch

def load_json(path: Path) -> Dict[str, Any]:
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}

def load_codeowners(path: Path) -> List[Tuple[str, List[str]]]:
    rules: List[Tuple[str, List[str]]] = []
    if not path.exists():
        return rules
    for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
        s = line.strip()
        if not s or s.startswith("#"):
            continue
        parts = s.split()
        if len(parts) >= 2:
            pattern = parts[0]
            owners = parts[1:]
            rules.append((pattern, owners))
    return rules

def match_owners(rel_path: str, rules: List[Tuple[str, List[str]]]) -> List[str]:
    owners: List[str] = []
    for pattern, pats_owners in rules:
        try:
            if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch("/" + rel_path, pattern):
                owners.extend(pats_owners)
        except Exception:
            continue
    # dedupe preserving order
    seen: set[str] = set()
    out: List[str] = []
    for o in owners:
        if o not in seen:
            seen.add(o)
            out.append(o)
    return out


    except Exception:
        return {}

def load_codeowners(path: Path) -> List[Tuple[str, List[str]]]:
    rules: List[Tuple[str, List[str]]] = []
    if not path.exists():
        return rules
    for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
        s = line.strip()
        if not s or s.startswith("#"):
            continue
        parts = s.split()
        if len(parts) >= 2:
            pattern = parts[0]
            owners = parts[1:]
            rules.append((pattern, owners))
    return rules

def match_owners(rel_path: str, rules: List[Tuple[str, List[str]]]) -> List[str]:
    owners: List[str] = []
    for pattern, pats_owners in rules:
        try:
            if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch("/" + rel_path, pattern):
                owners.extend(pats_owners)
        except Exception:
            continue
    # dedupe preserving order
    seen: set[str] = set()
    out: List[str] = []
    for o in owners:
        if o not in seen:
            seen.add(o)
            out.append(o)
    return out

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    idx_path = root / "data" / "sandbox" / "tmp" / "index.json"
    codeowners_path = root / "CODEOWNERS"
    out_path = root / "data" / "sandbox" / "tmp" / "plan_impact.json"

    idx = load_json(idx_path)
    entries: List[Dict[str, Any]] = idx.get("entries", []) or []
    rules = load_codeowners(codeowners_path)

    files_impact: List[Dict[str, Any]] = []
    for it in entries:
        file_abs = str(it.get("file", ""))
        if not file_abs:
            continue
        try:
            rel = str(Path(file_abs).resolve().relative_to(root))
        except Exception:


def match_owners(rel_path: str, rules: List[Tuple[str, List[str]]]) -> List[str]:
    owners: List[str] = []
    for pattern, pats_owners in rules:
        try:
            if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch("/" + rel_path, pattern):
                owners.extend(pats_owners)
        except Exception:
            continue
    # dedupe preserving order
    seen: set[str] = set()
    out: List[str] = []
    for o in owners:
        if o not in seen:
            seen.add(o)
            out.append(o)
    return out

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    idx_path = root / "data" / "sandbox" / "tmp" / "index.json"
    codeowners_path = root / "CODEOWNERS"
    out_path = root / "data" / "sandbox" / "tmp" / "plan_impact.json"

    idx = load_json(idx_path)
    entries: List[Dict[str, Any]] = idx.get("entries", []) or []
    rules = load_codeowners(codeowners_path)

    files_impact: List[Dict[str, Any]] = []
    for it in entries:
        file_abs = str(it.get("file", ""))
        if not file_abs:
            continue
        try:
            rel = str(Path(file_abs).resolve().relative_to(root))
        except Exception:
            rel = file_abs
        defs = it.get("defs", []) or []
        owners = match_owners(rel, rules)
        files_impact.append(
            {
                "path": rel,
                "symbols": len(defs),
                "owners": owners,
            }
        )

    # Aggregate modules (top-N by symbol count)
    top = sorted(files_impact, key=lambda x: int(x.get("symbols", 0)), reverse=True)[:50]
    owners_all: List[str] = []
    for x in top:
        for o in x.get("owners", []) or []:
            if o not in owners_all:
                owners_all.append(o)

    out = {
        "ok": True,
        "impact": {
            "top_modules": top,
            "owners": owners_all,
        },
        "sources": {"index": str(idx_path), "codeowners": str(codeowners_path)},
        "out": str(out_path),
    }

    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out_path)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Any, Dict, List

def slice_ir(index: Dict[str, Any], files: List[str], max_chars: int = 8000) -> Dict[str, Any]:
    funcs = (index.get("functions") or {}) if isinstance(index, dict) else {}
    classes = (index.get("classes") or {}) if isinstance(index, dict) else {}
    calls = (index.get("calls") or {}) if isinstance(index, dict) else {}
    out: Dict[str, Any] = {"functions": {}, "classes": {}, "calls": {}}
    for fp in files:
        if fp in funcs:
            out["functions"][fp] = funcs[fp][:200]
        if fp in classes:
            out["classes"][fp] = classes[fp][:200]
        if fp in calls:
            out["calls"][fp] = calls[fp][:200]
    return out

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    idx_path = root / "data" / "sandbox" / "tmp" / "index.json"
    plan_path = root / "data" / "sandbox" / "plan" / "plan.json"
    out = root / "data" / "sandbox" / "tmp" / "prompt_pack.json"
    try:
        idx = json.loads(idx_path.read_text(encoding="utf-8")) if idx_path.exists() else {}
    except Exception:
        idx = {}
    try:
        plan_obj = json.loads(plan_path.read_text(encoding="utf-8")) if plan_path.exists() else {}
    except Exception:
        plan_obj = {}
    prs = (plan_obj.get("plan", {}) or {}).get("prs", []) if isinstance(plan_obj, dict) else []
    files: List[str] = []
    for pr in prs:
        files.extend([str(f) for f in pr.get("files", [])][:20])
    ir_slice = slice_ir(idx.get("graph", {}), files)
    pack = {
        "ok": True,
        "policy_caps": (plan_obj.get("plan", {}) or {}).get("caps", {}),
        "files": files[:100],
        "ir_slice": ir_slice,
    }
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(json.dumps(pack, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Any, Dict, List

def slice_ir(index: Dict[str, Any], files: List[str], max_chars: int = 8000) -> Dict[str, Any]:
    funcs = (index.get("functions") or {}) if isinstance(index, dict) else {}
    classes = (index.get("classes") or {}) if isinstance(index, dict) else {}
    calls = (index.get("calls") or {}) if isinstance(index, dict) else {}
    out: Dict[str, Any] = {"functions": {}, "classes": {}, "calls": {}}
    for fp in files:
        if fp in funcs:
            out["functions"][fp] = funcs[fp][:200]
        if fp in classes:
            out["classes"][fp] = classes[fp][:200]
        if fp in calls:
            out["calls"][fp] = calls[fp][:200]
    return out

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    idx_path = root / "data" / "sandbox" / "tmp" / "index.json"
    plan_path = root / "data" / "sandbox" / "plan" / "plan.json"
    out = root / "data" / "sandbox" / "tmp" / "prompt_pack.json"
    try:
        idx = json.loads(idx_path.read_text(encoding="utf-8")) if idx_path.exists() else {}
    except Exception:
        idx = {}
    try:
        plan_obj = json.loads(plan_path.read_text(encoding="utf-8")) if plan_path.exists() else {}
    except Exception:
        plan_obj = {}
    prs = (plan_obj.get("plan", {}) or {}).get("prs", []) if isinstance(plan_obj, dict) else []
    files: List[str] = []
    for pr in prs:
        files.extend([str(f) for f in pr.get("files", [])][:20])
    ir_slice = slice_ir(idx.get("graph", {}), files)


import json
from pathlib import Path
from typing import Any, Dict, List

def slice_ir(index: Dict[str, Any], files: List[str], max_chars: int = 8000) -> Dict[str, Any]:
    funcs = (index.get("functions") or {}) if isinstance(index, dict) else {}
    classes = (index.get("classes") or {}) if isinstance(index, dict) else {}
    calls = (index.get("calls") or {}) if isinstance(index, dict) else {}
    out: Dict[str, Any] = {"functions": {}, "classes": {}, "calls": {}}
    for fp in files:
        if fp in funcs:
            out["functions"][fp] = funcs[fp][:200]
        if fp in classes:
            out["classes"][fp] = classes[fp][:200]
        if fp in calls:
            out["calls"][fp] = calls[fp][:200]
    return out

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    idx_path = root / "data" / "sandbox" / "tmp" / "index.json"
    plan_path = root / "data" / "sandbox" / "plan" / "plan.json"
    out = root / "data" / "sandbox" / "tmp" / "prompt_pack.json"
    try:
        idx = json.loads(idx_path.read_text(encoding="utf-8")) if idx_path.exists() else {}
    except Exception:
        idx = {}
    try:
        plan_obj = json.loads(plan_path.read_text(encoding="utf-8")) if plan_path.exists() else {}
    except Exception:
        plan_obj = {}
    prs = (plan_obj.get("plan", {}) or {}).get("prs", []) if isinstance(plan_obj, dict) else []
    files: List[str] = []
    for pr in prs:
        files.extend([str(f) for f in pr.get("files", [])][:20])
    ir_slice = slice_ir(idx.get("graph", {}), files)
    pack = {
        "ok": True,
        "policy_caps": (plan_obj.get("plan", {}) or {}).get("caps", {}),
        "files": files[:100],
        "ir_slice": ir_slice,
    }
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(json.dumps(pack, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


from __future__ import annotations

import json
import os
import uuid
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any, Dict, List, Optional

ROOT = Path(__file__).resolve().parents[2]

def ensure_dirs() -> None:
    for p in [
        ROOT / "data" / "foundry" / "backlog",
        ROOT / "data" / "foundry" / "repos",
        ROOT / "artifacts" / "foundry",
        ROOT / "data" / "traces" / "foundry_runs",
        ROOT / "data" / "sandbox" / "sft",
    ]:
        p.mkdir(parents=True, exist_ok=True)

def write_json(path: Path, obj: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")

def safe_slug(text: str) -> str:
    s = "".join(c if c.isalnum() or c in ("-", "_") else "-" for c in text.lower())
    while "--" in s:
        s = s.replace("--", "-")
    return s.strip("-")

@dataclass
class RepoBrief:
    id: str
    archetype: str
    objective: str
    acceptance: Dict[str, Any]
    curriculum_tag: str
    difficulty: int
    seed_refs: List[str]
    scoring_weights: Dict[str, float]

    def to_yaml(self) -> str:
        # Minimal YAML without deps; avoid adding PyYAML dependency
        def dump(d: Any, indent: int = 0) -> str:
            sp = "  " * indent
            if isinstance(d, dict):
                lines = []
                for k, v in d.items():
                    if isinstance(v, (dict, list)):
                        lines.append(f"{sp}{k}:")
                        lines.append(dump(v, indent + 1))
                    else:
                        val = json.dumps(v, ensure_ascii=False)
                        lines.append(f"{sp}{k}: {val}")
                return "\n".join(lines)
            if isinstance(d, list):
                lines = []
                for v in d:
                    if isinstance(v, (dict, list)):
                        lines.append(f"{sp}-")
                        lines.append(dump(v, indent + 1))
                    else:
                        val = json.dumps(v, ensure_ascii=False)
                        lines.append(f"{sp}- {val}")
                return "\n".join(lines)
            return f"{sp}{json.dumps(d, ensure_ascii=False)}"

        d = asdict(self)
        # order keys for readability
        ordered = {
            "id": d["id"],
            "archetype": d["archetype"],
            "objective": d["objective"],
            "acceptance": d["acceptance"],
            "curriculum_tag": d["curriculum_tag"],
            "difficulty": d["difficulty"],
            "seed_refs": d["seed_refs"],
            "scoring_weights": d["scoring_weights"],
        }
        return dump(ordered) + "\n"

def default_briefs(n: int, date_str: str) -> List[RepoBrief]:
    briefs: List[RepoBrief] = []
    archetypes = [
        "python-cli",
        "lib-algo",
        "web-min",
        "data-pipeline-mini",
        "codemod",
        "bench-adapter",
        "repo-surgery",
    ]
    seeds = [
        ("foundations/string/uuidv5_cli", "Implement a deterministic text->UUIDv5 CLI with plugin architecture."),
        ("foundations/graph/topo_sort_lib", "Implement topological sort with cycle detection and property tests."),
        ("io/csv/jsonl_sampler", "Stream sample JSONL from CSV with bounded memory."),
        ("perf/regex_router", "Implement O(n) regex router avoiding backtracking pitfalls."),
        ("codemod/rename_pkg_ns", "Codemod to rename package namespace and update imports idempotently."),
        ("web_min/fastapi_health_counter", "FastAPI service with health and counter; meet p95 latency."),
        ("data_pipeline/schema_enforcer", "Enforce schema with pydantic and golden schemas."),
        ("bench_adapter/mbpp_runner", "Adapter to run MBPP items and export unified traces."),
        ("repo_surgery/dry_move_scripts", "Refactor: move scripts to tools and update refs deterministically."),
        ("concurrency/async_pool", "Async task pool with bounded concurrency and graceful shutdown."),
    ]
    for i in range(max(1, n)):
        tag, obj = seeds[i % len(seeds)]
        arche = archetypes[i % len(archetypes)]
        bid = f"{tag.split('/')[-1]}-{uuid.uuid4().hex[:8]}"
        briefs.append(
            RepoBrief(
                id=bid,
                archetype=arche,
                objective=obj,
                acceptance={
                    "tests": ["pytest -q"],
                    "gates": {
                        "typecheck": True,
                        "lint": True,
                        "perf_budget_ms_p95": 50,
                        "risk_caps": {"max_files": 3, "max_lines": 200, "max_risk": 0.2},
                    },
                },
                curriculum_tag=tag,
                difficulty=1,
                seed_refs=["rfc:4122"],
                scoring_weights={
                    "pass": 0.6,
                    "diff_minimality": 0.1,
                    "perf": 0.1,
                    "test_coverage": 0.1,
                    "policy_compliance": 0.1,
                },
            )
        )
    return briefs

def write_run_append(run_id: str, entry: Dict[str, Any]) -> Path:
    out = ROOT / "data" / "traces" / "foundry_runs" / run_id / "run.jsonl"
    out.parent.mkdir(parents=True, exist_ok=True)
    with out.open("a", encoding="utf-8") as f:
        f.write(json.dumps(entry, ensure_ascii=False) + "\n")
    return out


from __future__ import annotations

import json
import os
import uuid
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any, Dict, List, Optional

ROOT = Path(__file__).resolve().parents[2]

def ensure_dirs() -> None:
    for p in [
        ROOT / "data" / "foundry" / "backlog",
        ROOT / "data" / "foundry" / "repos",
        ROOT / "artifacts" / "foundry",
        ROOT / "data" / "traces" / "foundry_runs",
        ROOT / "data" / "sandbox" / "sft",
    ]:
        p.mkdir(parents=True, exist_ok=True)

def write_json(path: Path, obj: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")

def safe_slug(text: str) -> str:
    s = "".join(c if c.isalnum() or c in ("-", "_") else "-" for c in text.lower())
    while "--" in s:
        s = s.replace("--", "-")
    return s.strip("-")

@dataclass
class RepoBrief:
    id: str
    archetype: str
    objective: str
    acceptance: Dict[str, Any]


import uuid
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any, Dict, List, Optional

ROOT = Path(__file__).resolve().parents[2]

def ensure_dirs() -> None:
    for p in [
        ROOT / "data" / "foundry" / "backlog",
        ROOT / "data" / "foundry" / "repos",
        ROOT / "artifacts" / "foundry",
        ROOT / "data" / "traces" / "foundry_runs",
        ROOT / "data" / "sandbox" / "sft",
    ]:
        p.mkdir(parents=True, exist_ok=True)

def write_json(path: Path, obj: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")

def safe_slug(text: str) -> str:
    s = "".join(c if c.isalnum() or c in ("-", "_") else "-" for c in text.lower())
    while "--" in s:
        s = s.replace("--", "-")
    return s.strip("-")

@dataclass
class RepoBrief:
    id: str
    archetype: str
    objective: str
    acceptance: Dict[str, Any]
    curriculum_tag: str
    difficulty: int
    seed_refs: List[str]
    scoring_weights: Dict[str, float]


ROOT = Path(__file__).resolve().parents[2]

def ensure_dirs() -> None:
    for p in [
        ROOT / "data" / "foundry" / "backlog",
        ROOT / "data" / "foundry" / "repos",
        ROOT / "artifacts" / "foundry",
        ROOT / "data" / "traces" / "foundry_runs",
        ROOT / "data" / "sandbox" / "sft",
    ]:
        p.mkdir(parents=True, exist_ok=True)

def write_json(path: Path, obj: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")

def safe_slug(text: str) -> str:
    s = "".join(c if c.isalnum() or c in ("-", "_") else "-" for c in text.lower())
    while "--" in s:
        s = s.replace("--", "-")
    return s.strip("-")

@dataclass
class RepoBrief:
    id: str
    archetype: str
    objective: str
    acceptance: Dict[str, Any]
    curriculum_tag: str
    difficulty: int
    seed_refs: List[str]
    scoring_weights: Dict[str, float]

    def to_yaml(self) -> str:
        # Minimal YAML without deps; avoid adding PyYAML dependency
        def dump(d: Any, indent: int = 0) -> str:
            sp = "  " * indent
            if isinstance(d, dict):
                lines = []
                for k, v in d.items():


RepoBrief

        ROOT / "artifacts" / "foundry",
        ROOT / "data" / "traces" / "foundry_runs",
        ROOT / "data" / "sandbox" / "sft",
    ]:
        p.mkdir(parents=True, exist_ok=True)

def write_json(path: Path, obj: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")

def safe_slug(text: str) -> str:
    s = "".join(c if c.isalnum() or c in ("-", "_") else "-" for c in text.lower())
    while "--" in s:
        s = s.replace("--", "-")
    return s.strip("-")

@dataclass
class RepoBrief:
    id: str
    archetype: str
    objective: str
    acceptance: Dict[str, Any]
    curriculum_tag: str
    difficulty: int
    seed_refs: List[str]
    scoring_weights: Dict[str, float]

    def to_yaml(self) -> str:
        # Minimal YAML without deps; avoid adding PyYAML dependency
        def dump(d: Any, indent: int = 0) -> str:
            sp = "  " * indent
            if isinstance(d, dict):
                lines = []
                for k, v in d.items():
                    if isinstance(v, (dict, list)):
                        lines.append(f"{sp}{k}:")
                        lines.append(dump(v, indent + 1))
                    else:
                        val = json.dumps(v, ensure_ascii=False)
                        lines.append(f"{sp}{k}: {val}")
                return "\n".join(lines)
            if isinstance(d, list):
                lines = []
                for v in d:
                    if isinstance(v, (dict, list)):
                        lines.append(f"{sp}-")
                        lines.append(dump(v, indent + 1))
                    else:
                        val = json.dumps(v, ensure_ascii=False)
                        lines.append(f"{sp}- {val}")
                return "\n".join(lines)
            return f"{sp}{json.dumps(d, ensure_ascii=False)}"

        d = asdict(self)
        # order keys for readability
        ordered = {
            "id": d["id"],
            "archetype": d["archetype"],
            "objective": d["objective"],
            "acceptance": d["acceptance"],
            "curriculum_tag": d["curriculum_tag"],
            "difficulty": d["difficulty"],
            "seed_refs": d["seed_refs"],
            "scoring_weights": d["scoring_weights"],
        }
        return dump(ordered) + "\n"

def default_briefs(n: int, date_str: str) -> List[RepoBrief]:
    briefs: List[RepoBrief] = []
    archetypes = [
        "python-cli",
        "lib-algo",
        "web-min",
        "data-pipeline-mini",
        "codemod",
        "bench-adapter",
        "repo-surgery",
    ]
    seeds = [
        ("foundations/string/uuidv5_cli", "Implement a deterministic text->UUIDv5 CLI with plugin architecture."),
        ("foundations/graph/topo_sort_lib", "Implement topological sort with cycle detection and property tests."),
        ("io/csv/jsonl_sampler", "Stream sample JSONL from CSV with bounded memory."),
        ("perf/regex_router", "Implement O(n) regex router avoiding backtracking pitfalls."),
        ("codemod/rename_pkg_ns", "Codemod to rename package namespace and update imports idempotently."),
        ("web_min/fastapi_health_counter", "FastAPI service with health and counter; meet p95 latency."),


                        val = json.dumps(v, ensure_ascii=False)
                        lines.append(f"{sp}- {val}")
                return "\n".join(lines)
            return f"{sp}{json.dumps(d, ensure_ascii=False)}"

        d = asdict(self)
        # order keys for readability
        ordered = {
            "id": d["id"],
            "archetype": d["archetype"],
            "objective": d["objective"],
            "acceptance": d["acceptance"],
            "curriculum_tag": d["curriculum_tag"],
            "difficulty": d["difficulty"],
            "seed_refs": d["seed_refs"],
            "scoring_weights": d["scoring_weights"],
        }
        return dump(ordered) + "\n"

def default_briefs(n: int, date_str: str) -> List[RepoBrief]:
    briefs: List[RepoBrief] = []
    archetypes = [
        "python-cli",
        "lib-algo",
        "web-min",
        "data-pipeline-mini",
        "codemod",
        "bench-adapter",
        "repo-surgery",
    ]
    seeds = [
        ("foundations/string/uuidv5_cli", "Implement a deterministic text->UUIDv5 CLI with plugin architecture."),
        ("foundations/graph/topo_sort_lib", "Implement topological sort with cycle detection and property tests."),
        ("io/csv/jsonl_sampler", "Stream sample JSONL from CSV with bounded memory."),
        ("perf/regex_router", "Implement O(n) regex router avoiding backtracking pitfalls."),
        ("codemod/rename_pkg_ns", "Codemod to rename package namespace and update imports idempotently."),
        ("web_min/fastapi_health_counter", "FastAPI service with health and counter; meet p95 latency."),
        ("data_pipeline/schema_enforcer", "Enforce schema with pydantic and golden schemas."),
        ("bench_adapter/mbpp_runner", "Adapter to run MBPP items and export unified traces."),
        ("repo_surgery/dry_move_scripts", "Refactor: move scripts to tools and update refs deterministically."),
        ("concurrency/async_pool", "Async task pool with bounded concurrency and graceful shutdown."),
    ]
    for i in range(max(1, n)):
        tag, obj = seeds[i % len(seeds)]
        arche = archetypes[i % len(archetypes)]
        bid = f"{tag.split('/')[-1]}-{uuid.uuid4().hex[:8]}"
        briefs.append(
            RepoBrief(
                id=bid,
                archetype=arche,
                objective=obj,
                acceptance={
                    "tests": ["pytest -q"],
                    "gates": {
                        "typecheck": True,
                        "lint": True,
                        "perf_budget_ms_p95": 50,
                        "risk_caps": {"max_files": 3, "max_lines": 200, "max_risk": 0.2},
                    },
                },
                curriculum_tag=tag,
                difficulty=1,
                seed_refs=["rfc:4122"],
                scoring_weights={
                    "pass": 0.6,
                    "diff_minimality": 0.1,
                    "perf": 0.1,
                    "test_coverage": 0.1,
                    "policy_compliance": 0.1,
                },
            )
        )
    return briefs

def write_run_append(run_id: str, entry: Dict[str, Any]) -> Path:
    out = ROOT / "data" / "traces" / "foundry_runs" / run_id / "run.jsonl"
    out.parent.mkdir(parents=True, exist_ok=True)
    with out.open("a", encoding="utf-8") as f:
        f.write(json.dumps(entry, ensure_ascii=False) + "\n")
    return out


                        "lint": True,
                        "perf_budget_ms_p95": 50,
                        "risk_caps": {"max_files": 3, "max_lines": 200, "max_risk": 0.2},
                    },
                },
                curriculum_tag=tag,
                difficulty=1,
                seed_refs=["rfc:4122"],
                scoring_weights={
                    "pass": 0.6,
                    "diff_minimality": 0.1,
                    "perf": 0.1,
                    "test_coverage": 0.1,
                    "policy_compliance": 0.1,
                },
            )
        )
    return briefs

def write_run_append(run_id: str, entry: Dict[str, Any]) -> Path:
    out = ROOT / "data" / "traces" / "foundry_runs" / run_id / "run.jsonl"
    out.parent.mkdir(parents=True, exist_ok=True)
    with out.open("a", encoding="utf-8") as f:
        f.write(json.dumps(entry, ensure_ascii=False) + "\n")
    return out


def safe_slug(text: str) -> str:
    s = "".join(c if c.isalnum() or c in ("-", "_") else "-" for c in text.lower())
    while "--" in s:
        s = s.replace("--", "-")
    return s.strip("-")

@dataclass
class RepoBrief:
    id: str
    archetype: str
    objective: str
    acceptance: Dict[str, Any]
    curriculum_tag: str
    difficulty: int
    seed_refs: List[str]
    scoring_weights: Dict[str, float]

    def to_yaml(self) -> str:
        # Minimal YAML without deps; avoid adding PyYAML dependency
        def dump(d: Any, indent: int = 0) -> str:
            sp = "  " * indent
            if isinstance(d, dict):
                lines = []
                for k, v in d.items():
                    if isinstance(v, (dict, list)):
                        lines.append(f"{sp}{k}:")
                        lines.append(dump(v, indent + 1))
                    else:
                        val = json.dumps(v, ensure_ascii=False)
                        lines.append(f"{sp}{k}: {val}")
                return "\n".join(lines)
            if isinstance(d, list):
                lines = []
                for v in d:
                    if isinstance(v, (dict, list)):
                        lines.append(f"{sp}-")
                        lines.append(dump(v, indent + 1))
                    else:
                        val = json.dumps(v, ensure_ascii=False)
                        lines.append(f"{sp}- {val}")
                return "\n".join(lines)
            return f"{sp}{json.dumps(d, ensure_ascii=False)}"

        d = asdict(self)
        # order keys for readability
        ordered = {
            "id": d["id"],
            "archetype": d["archetype"],
            "objective": d["objective"],
            "acceptance": d["acceptance"],
            "curriculum_tag": d["curriculum_tag"],
            "difficulty": d["difficulty"],
            "seed_refs": d["seed_refs"],
            "scoring_weights": d["scoring_weights"],
        }
        return dump(ordered) + "\n"

def default_briefs(n: int, date_str: str) -> List[RepoBrief]:
    briefs: List[RepoBrief] = []
    archetypes = [
        "python-cli",
        "lib-algo",
        "web-min",
        "data-pipeline-mini",
        "codemod",
        "bench-adapter",
        "repo-surgery",
    ]
    seeds = [
        ("foundations/string/uuidv5_cli", "Implement a deterministic text->UUIDv5 CLI with plugin architecture."),
        ("foundations/graph/topo_sort_lib", "Implement topological sort with cycle detection and property tests."),
        ("io/csv/jsonl_sampler", "Stream sample JSONL from CSV with bounded memory."),
        ("perf/regex_router", "Implement O(n) regex router avoiding backtracking pitfalls."),
        ("codemod/rename_pkg_ns", "Codemod to rename package namespace and update imports idempotently."),
        ("web_min/fastapi_health_counter", "FastAPI service with health and counter; meet p95 latency."),


dump

def safe_slug(text: str) -> str:
    s = "".join(c if c.isalnum() or c in ("-", "_") else "-" for c in text.lower())
    while "--" in s:
        s = s.replace("--", "-")
    return s.strip("-")

@dataclass
class RepoBrief:
    id: str
    archetype: str
    objective: str
    acceptance: Dict[str, Any]
    curriculum_tag: str
    difficulty: int
    seed_refs: List[str]
    scoring_weights: Dict[str, float]

    def to_yaml(self) -> str:
        # Minimal YAML without deps; avoid adding PyYAML dependency
        def dump(d: Any, indent: int = 0) -> str:
            sp = "  " * indent
            if isinstance(d, dict):
                lines = []
                for k, v in d.items():
                    if isinstance(v, (dict, list)):
                        lines.append(f"{sp}{k}:")
                        lines.append(dump(v, indent + 1))
                    else:
                        val = json.dumps(v, ensure_ascii=False)
                        lines.append(f"{sp}{k}: {val}")
                return "\n".join(lines)
            if isinstance(d, list):
                lines = []
                for v in d:
                    if isinstance(v, (dict, list)):
                        lines.append(f"{sp}-")
                        lines.append(dump(v, indent + 1))
                    else:
                        val = json.dumps(v, ensure_ascii=False)
                        lines.append(f"{sp}- {val}")
                return "\n".join(lines)
            return f"{sp}{json.dumps(d, ensure_ascii=False)}"

        d = asdict(self)
        # order keys for readability
        ordered = {
            "id": d["id"],
            "archetype": d["archetype"],
            "objective": d["objective"],
            "acceptance": d["acceptance"],
            "curriculum_tag": d["curriculum_tag"],
            "difficulty": d["difficulty"],
            "seed_refs": d["seed_refs"],
            "scoring_weights": d["scoring_weights"],
        }
        return dump(ordered) + "\n"

def default_briefs(n: int, date_str: str) -> List[RepoBrief]:
    briefs: List[RepoBrief] = []
    archetypes = [
        "python-cli",


#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
from pathlib import Path

from .common import ROOT, write_json

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--tags", default="foundations/*")
    ap.add_argument("--run-id")
    args = ap.parse_args()
    out = ROOT / "data" / "sandbox" / "tmp" / "curriculum_update.json"
    out.parent.mkdir(parents=True, exist_ok=True)
    write_json(out, {"ok": True, "tags": args.tags})
    print(json.dumps({"ok": True, "out": str(out)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import re
from pathlib import Path

from .common import ROOT, ensure_dirs, safe_slug

def parse_brief_yaml(path: Path) -> dict:
    # Minimal YAML reader for constrained brief format (keys, lists, scalars)
    text = path.read_text(encoding="utf-8")
    # Very naive; acceptable for initial bootstrap
    data: dict = {}
    stack = [data]
    indent_stack = [0]
    key_stack: list[str] = []
    for line in text.splitlines():
        if not line.strip() or line.strip().startswith("#"):
            continue
        indent = len(line) - len(line.lstrip(" "))
        while indent < indent_stack[-1]:
            stack.pop()
            indent_stack.pop()
            if key_stack:
                key_stack.pop()
        if ":" in line:
            k, v = line.strip().split(":", 1)
            k = k.strip()
            v = v.strip()
            if not v:
                # nested dict
                cur = {}
                stack[-1][k] = cur
                stack.append(cur)
                indent_stack.append(indent + 2)
                key_stack.append(k)
            else:
                # parse JSON-ish scalars
                v = v.strip()
                if v.lower() in ("true", "false"):
                    val = v.lower() == "true"
                else:
                    try:
                        val = json.loads(v)
                    except Exception:
                        val = v.strip('"')
                stack[-1][k] = val
        elif line.strip().startswith("-"):
            item = line.strip()[1:].strip()
            try:
                val = json.loads(item) if item else None
            except Exception:
                val = item.strip('"')
            # attach to last mapping key
            if not key_stack:
                continue
            parent = stack[-1]
            last_key = key_stack[-1]
            if not isinstance(parent.get(last_key), list):
                parent[last_key] = []
            parent[last_key].append(val)
    return data

def write_python_cli_repo(repo_dir: Path, brief: dict) -> None:
    pkg = repo_dir / "src" / "app"
    tests = repo_dir / "tests"
    scripts = repo_dir / "scripts" / "ci"
    pkg.mkdir(parents=True, exist_ok=True)
    tests.mkdir(parents=True, exist_ok=True)
    scripts.mkdir(parents=True, exist_ok=True)
    (repo_dir / "pyproject.toml").write_text(
        """
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "app"
version = "0.0.1"
requires-python = ">=3.8"
dependencies = ["pytest"]
""".strip()
        + "\n",
        encoding="utf-8",
    )
    (pkg / "__init__.py").write_text("\n", encoding="utf-8")
    (pkg / "cli.py").write_text(
        """
import argparse
import sys
import uuid

def text_to_uuid(name: str) -> str:
    return str(uuid.uuid5(uuid.NAMESPACE_DNS, name))

def main(argv=None) -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("text")
    args = ap.parse_args(argv)
    print(text_to_uuid(args.text))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
""".strip()
        + "\n",
        encoding="utf-8",
    )
    (tests / "test_cli.py").write_text(
        """
from src.app.cli import text_to_uuid

def test_uuid_deterministic():
    a = text_to_uuid("hello")
    b = text_to_uuid("hello")
    assert a == b and len(a) == 36
""".strip()
        + "\n",
        encoding="utf-8",
    )
    (scripts / "gates.py").write_text(
        """
#!/usr/bin/env python3
import shutil, subprocess, json

def run(cmd):
    res = subprocess.run(cmd, shell=True)
    return res.returncode

def main():
    tests = run("pytest -q") if shutil.which("pytest") else 0
    types = run("mypy .") if shutil.which("mypy") else 0
    lint = run("flake8 .") if shutil.which("flake8") else 0
    ok = (tests==0 and types==0 and lint==0)
    print(json.dumps({"ok": ok, "rc": {"pytest": tests, "mypy": types, "flake8": lint}}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
""".strip()
        + "\n",
        encoding="utf-8",
    )

def scaffold_from_brief(brief_path: Path, run_id: str) -> Path:
    brief = parse_brief_yaml(brief_path)
    slug = safe_slug(brief.get("id") or "repo")
    repo_dir = ROOT / "data" / "foundry" / "repos" / run_id / slug
    repo_dir.mkdir(parents=True, exist_ok=True)
    arche = (brief.get("archetype") or "python-cli").lower()
    if arche in ("python-cli", "python_cli", "python-cli|lib|web-min|codemod|bench-adapter"):
        write_python_cli_repo(repo_dir, brief)
    else:
        write_python_cli_repo(repo_dir, brief)
    # Write policies stub
    (repo_dir / "policies.yaml").write_text("risk_caps:\n  max_files: 3\n  max_lines: 200\n  max_risk: 0.2\n", encoding="utf-8")
    return repo_dir

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--brief", required=True)
    ap.add_argument("--run-id", required=True)
    args = ap.parse_args()
    ensure_dirs()
    repo = scaffold_from_brief(Path(args.brief), args.run_id)
    print(str(repo))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())