		try:
			rel = p.relative_to(root)
		except Exception:
			rel = p
		parts = rel.parts
		if any(seg.startswith(".") for seg in parts):
			continue
		# Avoid indexing repo-internal artifacts directories
		if any(seg in ("data", "models", ".git") for seg in parts):
			continue
		if include_exts and p.suffix not in include_exts:
			continue
		files.append(p)
	return files

def tokenize(text: str) -> List[str]:
	return [t.lower() for t in TOKEN_RE.findall(text)]

def build_index(root: Path, out_path: Path, include_exts: List[str]) -> Dict[str, Any]:
	root = root.resolve()
	out_path.parent.mkdir(parents=True, exist_ok=True)
	files = iter_repo_files(root, include_exts)
	# Document statistics
	doc_len: Dict[str, int] = {}
	term_df: Dict[str, int] = {}
	postings: Dict[str, Dict[str, int]] = {}
	for fp in files:
		try:
			text = fp.read_text(encoding="utf-8", errors="ignore")
		except Exception:
			text = ""
		tokens = tokenize(text)
		doc_len[fp.as_posix()] = len(tokens)
		seen: Dict[str, int] = {}
		for tok in tokens:
			seen[tok] = seen.get(tok, 0) + 1
		for tok, tf in seen.items():
			postings.setdefault(tok, {})[fp.as_posix()] = tf
			term_df[tok] = term_df.get(tok, 0) + 1
	N = max(1, len(doc_len))
	avgdl = sum(doc_len.values()) / float(N)
	index = {
		"version": "0.1",
		"root": root.as_posix(),
		"docs": doc_len,
		"df": term_df,
		"postings": postings,
		"avgdl": avgdl,
		"N": N,
	}
	out_path.write_text(json.dumps(index, ensure_ascii=False, indent=2), encoding="utf-8")
	return index

def main() -> int:
	ap = argparse.ArgumentParser(description="Build simple BM25-like index for code/docs")
	root = Path(__file__).resolve().parents[2]
	ap.add_argument("--root", default=str(root))
	default_out = Path(__file__).resolve().parents[1] / "data" / "sandbox" / "tmp" / "bm25_index.json"
	ap.add_argument("--out", default=str(default_out))
	ap.add_argument("--exts", default=".py,.md,.mk,.txt,.yaml,.yml,.toml,.ini")
	args = ap.parse_args()

	include_exts = [e.strip() for e in str(args.exts).split(",") if e.strip()]
	idx = build_index(Path(args.root), Path(args.out), include_exts)
	print(json.dumps({"ok": True, "wrote": str(args.out), "docs": len(idx.get("docs", {}))}))
	return 0

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


main

		for tok in tokens:
			seen[tok] = seen.get(tok, 0) + 1
		for tok, tf in seen.items():
			postings.setdefault(tok, {})[fp.as_posix()] = tf
			term_df[tok] = term_df.get(tok, 0) + 1
	N = max(1, len(doc_len))
	avgdl = sum(doc_len.values()) / float(N)
	index = {
		"version": "0.1",
		"root": root.as_posix(),
		"docs": doc_len,
		"df": term_df,
		"postings": postings,
		"avgdl": avgdl,
		"N": N,
	}
	out_path.write_text(json.dumps(index, ensure_ascii=False, indent=2), encoding="utf-8")
	return index

def main() -> int:
	ap = argparse.ArgumentParser(description="Build simple BM25-like index for code/docs")
	root = Path(__file__).resolve().parents[2]
	ap.add_argument("--root", default=str(root))
	default_out = Path(__file__).resolve().parents[1] / "data" / "sandbox" / "tmp" / "bm25_index.json"
	ap.add_argument("--out", default=str(default_out))
	ap.add_argument("--exts", default=".py,.md,.mk,.txt,.yaml,.yml,.toml,.ini")
	args = ap.parse_args()

	include_exts = [e.strip() for e in str(args.exts).split(",") if e.strip()]
	idx = build_index(Path(args.root), Path(args.out), include_exts)
	print(json.dumps({"ok": True, "wrote": str(args.out), "docs": len(idx.get("docs", {}))}))
	return 0

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


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

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

def main() -> int:
	ap = argparse.ArgumentParser(description="Minimal QA planner hook")
	root = Path(__file__).resolve().parents[2]
	ap.add_argument("--question", required=True)
	ap.add_argument("--bm25", default=str(root / "data" / "sandbox" / "tmp" / "bm25_index.json"))
	ap.add_argument("--embed", default=str(root / "data" / "sandbox" / "tmp" / "embed_index.json"))
	ap.add_argument("--k", type=int, default=20)
	ap.add_argument("--min-conf", type=float, default=0.6)
	args = ap.parse_args()

	# Heuristic: if both indexes exist, propose hybrid; else BM25 only
	bm25_exists = Path(args.bm25).exists()
	embed_exists = Path(args.embed).exists()
	plan: Dict[str, Any] = {"actions": []}
	if bm25_exists and embed_exists:
		plan["actions"].append({"tool": "code_search.hybrid", "args": {"k": int(args.k)}})
		plan["confidence"] = 0.85
	elif bm25_exists:
		plan["actions"].append({"tool": "code_search.bm25", "args": {"k": int(args.k)}})
		plan["confidence"] = 0.7
	else:
		plan["actions"].append({"tool": "noop", "args": {}})
		plan["confidence"] = 0.2
	print(json.dumps(plan))
	return 0

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


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

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

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

def validate(citations: List[Dict[str, Any]], repo_root: Path, min_files: int = 1, min_lines_total: int = 20) -> Dict[str, Any]:
	ok = True
	checked: List[Dict[str, Any]] = []
	total_lines = 0
	good_files: set[str] = set()
	for c in citations:
		path = str(c.get("path", ""))
		start = int(c.get("start", 1))
		end = int(c.get("end", start))
		pp = Path(path)
		if not pp.is_absolute():
			pp = (repo_root / pp).resolve()
		exists = pp.exists() and pp.is_file()
		valid_range = True
		lines_in_range = 0
		if exists:
			try:
				n = len(pp.read_text(encoding="utf-8", errors="ignore").splitlines())
				valid_range = (1 <= start <= end <= max(1, n))
				if valid_range:
					lines_in_range = max(0, end - start + 1)
			except Exception:
				valid_range = False
		if exists and valid_range:
			total_lines += lines_in_range
			good_files.add(str(pp))
		ok = ok and exists and valid_range
		checked.append({"path": str(pp), "exists": exists, "start": start, "end": end, "valid_range": valid_range, "lines": lines_in_range})
	coverage_ok = (len(good_files) >= int(min_files)) and (total_lines >= int(min_lines_total))
	return {"ok": ok and coverage_ok, "checked": checked, "coverage": {"files": len(good_files), "lines": total_lines, "min_files": int(min_files), "min_lines_total": int(min_lines_total)}}

def main() -> int:
	ap = argparse.ArgumentParser(description="Validate citations against repo")
	root = Path(__file__).resolve().parents[2]
	ap.add_argument("--world", default=str(root / "data" / "sandbox" / "tmp" / "world.json"))
	ap.add_argument("--citations", default="")
	ap.add_argument("--min-files", type=int, default=1)
	ap.add_argument("--min-lines", type=int, default=20)
	args = ap.parse_args()

	world = load_json_safe(Path(args.world))
	repo_root = Path(world.get("repo_root", root.as_posix()))
	try:
		cits = json.loads(Path(args.citations).read_text(encoding="utf-8")) if args.citations else []
	except Exception:
		cits = []
	res = validate(cits, repo_root, min_files=int(args.min_files), min_lines_total=int(args.min_lines))
	print(json.dumps(res, ensure_ascii=False))
	return 0

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


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

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

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

def validate(citations: List[Dict[str, Any]], repo_root: Path, min_files: int = 1, min_lines_total: int = 20) -> Dict[str, Any]:
	ok = True
	checked: List[Dict[str, Any]] = []
	total_lines = 0
	good_files: set[str] = set()
	for c in citations:
		path = str(c.get("path", ""))
		start = int(c.get("start", 1))
		end = int(c.get("end", start))
		pp = Path(path)
		if not pp.is_absolute():
			pp = (repo_root / pp).resolve()
		exists = pp.exists() and pp.is_file()
		valid_range = True
		lines_in_range = 0
		if exists:
			try:
				n = len(pp.read_text(encoding="utf-8", errors="ignore").splitlines())


validate

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

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

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

def validate(citations: List[Dict[str, Any]], repo_root: Path, min_files: int = 1, min_lines_total: int = 20) -> Dict[str, Any]:
	ok = True
	checked: List[Dict[str, Any]] = []
	total_lines = 0
	good_files: set[str] = set()
	for c in citations:
		path = str(c.get("path", ""))
		start = int(c.get("start", 1))
		end = int(c.get("end", start))
		pp = Path(path)
		if not pp.is_absolute():
			pp = (repo_root / pp).resolve()
		exists = pp.exists() and pp.is_file()
		valid_range = True
		lines_in_range = 0
		if exists:
			try:
				n = len(pp.read_text(encoding="utf-8", errors="ignore").splitlines())
				valid_range = (1 <= start <= end <= max(1, n))
				if valid_range:
					lines_in_range = max(0, end - start + 1)
			except Exception:
				valid_range = False
		if exists and valid_range:
			total_lines += lines_in_range
			good_files.add(str(pp))
		ok = ok and exists and valid_range
		checked.append({"path": str(pp), "exists": exists, "start": start, "end": end, "valid_range": valid_range, "lines": lines_in_range})
	coverage_ok = (len(good_files) >= int(min_files)) and (total_lines >= int(min_lines_total))
	return {"ok": ok and coverage_ok, "checked": checked, "coverage": {"files": len(good_files), "lines": total_lines, "min_files": int(min_files), "min_lines_total": int(min_lines_total)}}

def main() -> int:
	ap = argparse.ArgumentParser(description="Validate citations against repo")
	root = Path(__file__).resolve().parents[2]
	ap.add_argument("--world", default=str(root / "data" / "sandbox" / "tmp" / "world.json"))
	ap.add_argument("--citations", default="")
	ap.add_argument("--min-files", type=int, default=1)
	ap.add_argument("--min-lines", type=int, default=20)
	args = ap.parse_args()

	world = load_json_safe(Path(args.world))
	repo_root = Path(world.get("repo_root", root.as_posix()))
	try:
		cits = json.loads(Path(args.citations).read_text(encoding="utf-8")) if args.citations else []
	except Exception:
		cits = []
	res = validate(cits, repo_root, min_files=int(args.min_files), min_lines_total=int(args.min_lines))
	print(json.dumps(res, ensure_ascii=False))
	return 0


main

		exists = pp.exists() and pp.is_file()
		valid_range = True
		lines_in_range = 0
		if exists:
			try:
				n = len(pp.read_text(encoding="utf-8", errors="ignore").splitlines())
				valid_range = (1 <= start <= end <= max(1, n))
				if valid_range:
					lines_in_range = max(0, end - start + 1)
			except Exception:
				valid_range = False
		if exists and valid_range:
			total_lines += lines_in_range
			good_files.add(str(pp))
		ok = ok and exists and valid_range
		checked.append({"path": str(pp), "exists": exists, "start": start, "end": end, "valid_range": valid_range, "lines": lines_in_range})
	coverage_ok = (len(good_files) >= int(min_files)) and (total_lines >= int(min_lines_total))
	return {"ok": ok and coverage_ok, "checked": checked, "coverage": {"files": len(good_files), "lines": total_lines, "min_files": int(min_files), "min_lines_total": int(min_lines_total)}}

def main() -> int:
	ap = argparse.ArgumentParser(description="Validate citations against repo")
	root = Path(__file__).resolve().parents[2]
	ap.add_argument("--world", default=str(root / "data" / "sandbox" / "tmp" / "world.json"))
	ap.add_argument("--citations", default="")
	ap.add_argument("--min-files", type=int, default=1)
	ap.add_argument("--min-lines", type=int, default=20)
	args = ap.parse_args()

	world = load_json_safe(Path(args.world))
	repo_root = Path(world.get("repo_root", root.as_posix()))
	try:
		cits = json.loads(Path(args.citations).read_text(encoding="utf-8")) if args.citations else []
	except Exception:
		cits = []
	res = validate(cits, repo_root, min_files=int(args.min_files), min_lines_total=int(args.min_lines))
	print(json.dumps(res, ensure_ascii=False))
	return 0

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


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.judge_longform')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_devtools_ds')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.verify_traces')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_dashboard_schema')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.emit_refactor_plan')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_scripts_alignment')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_planner_pref')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.pattern_based_expander')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.run_practice_suite')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.calibrate_planner_rerank')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.nightly_promotion')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.archive_makefile')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_code_quality')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_external')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.redteam_dom')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.validate_refactor_plan')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.bench.run_llm_bench')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.eval_probes')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.generate_router_negatives')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.generate_practice_curriculum')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.devtools.sandbox_exec')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.generate_curriculum')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.peek_actuator_preds')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.devtools.dev_loop_foreground')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.emit_plan')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_dom_verify')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_probes')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_actuator_il_splits')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_dashboard')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_balanced_traces')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.offpolicy_propose_repairs')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_wm_ds')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.promote_skills')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.count_models')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.loops.run_loop_webdom')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_code')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.train.train_actuator_il')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.init_llm_thresholds')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.run_external_bench')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_registry')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.devtools.dev_smoke')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.create_tiny_repos')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.snapshot_base_models')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.orchestrate_bench')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_actuator_il_combined')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.lint_code_samples')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.validate_patch_diff')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.create_comprehensive_dataset')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.loops.run_loop_dev')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.seed_traces')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.docs.generate_docs_index')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.nightly_il')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.update_all')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.ablate_wm_screen')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.run_code_bench')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.devtools.multi_agent_orchestrator')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_router_lift')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_router_metrics')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.seed_web_dom')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.move_completed_issues')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_wm_planrank')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.train.train_router')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.devtools.rewrite_imports')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.spreadsheet_demo')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.loops.run_loop_oscli')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.generate_design_doc')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.generate_scripts_index')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.validate_traces')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_wm')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_code_style_multi')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.dashboard.summarize_trajectories')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.fix_indentation')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.reward_shaping')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.apply_refactor_plan')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_wm_online')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.generate_capabilities_report')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.rotate_proxy_env')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_actuator_il')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.eval_actuator_dom_t5')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.docs.run_docs_suite')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.bench.code_review_critic')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.run_dev_loop_tests')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_planner_splits')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.run_refactor_cycle')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.train.train_verifier_qlora')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.convert_inspiration_tasks')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.dashboard.aggregate_llm_bench')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.eval_actuator_t5')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.dashboard.summarize_metrics')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.registry_snapshot')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_strict_match')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.run_benchmarks')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_docs')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.offpolicy_suggest')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.web_dom_fetch')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.eval_wm_planrank')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_dashboard_diff')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.devtools.updater')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_wm_splits')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.dashboard.aggregate_code_bench')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_router')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.devtools.sandbox_exec_multi')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.dashboard.summarize_dom_verify')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_llm')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_router_ds')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.attach_skill_adapter')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.validate_plan')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.train.train_verifier_calib')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.eval_adversarial_dom')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.train.train_wm_mlp')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.devtools.task_scheduler')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.compute_batch_audit')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.generate_external_suite')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_splits')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.contamination_check')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.unify_traces')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.run_redteam_dom')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_verifier_ds')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.check_make_modularization')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.dedupe_json')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.run_lm_eval')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_verifier_calib')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.web_dom_trace')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.massive_task_extractor')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_dom_latency')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_verifier_splits')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.devtools.devtools_orchestrator')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_safe_edits')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.plan_tot')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.train.train_planner_ppo')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_code_style')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.ci_matrix')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.eval_router_oscli')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_redteam_dom')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_devtools')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.dashboard.aggregate_dashboard')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.lint_multilang_samples')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_secrets')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.offpolicy_trainer')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.query_memory')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.misc.promote_repairs_to_il')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.data.snapshot_env')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_near_miss_replay')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.ci_assert_bench')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.eval.eval_actuator_il')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


import logging
import importlib, sys
mod = importlib.import_module('scripts.build.build_memory')
sys.exit(getattr(mod, 'main', lambda:0)() if hasattr(mod, 'main') else 0)


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Enrich apprepo spec with routes, versions, layout, styling, env, verify plan")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--inp", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.json"))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.enriched.json"))
    return ap.parse_args()

def _parse_package(pkg_text: str) -> Dict[str, Any]:
    try:
        return json.loads(pkg_text)
    except Exception:
        return {}

def _derive_routes(pages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    routes: List[Dict[str, Any]] = []
    for it in pages or []:
        p = str(it.get("path", ""))
        route = None
        if "/app/" in p:
            try:
                sub = p.split("/app/")[1]
                # remove trailing file name
                sub = sub.replace("/page.tsx", "").rstrip("/")
                route = "/" + sub
                if route == "/":
                    route = "/"
            except Exception:
                route = None
        routes.append({"file": p, "path": route})
    return routes

def _detect_providers(layout_preview: str) -> List[str]:
    providers: List[str] = []
    for name in ("ClerkProvider", "ConvexProvider", "ConvexProviderWithClerk"):
        if name in layout_preview:
            providers.append(name)
    return providers

def _detect_env(previews: List[str]) -> List[str]:
    envs: set[str] = set()
    pat = re.compile(r"process\.env\.([A-Z0-9_]+)")
    for text in previews:
        for m in pat.findall(text or ""):
            envs.add(m)
    return sorted(envs)

def main() -> int:
    args = parse_args()
    inp = Path(args.inp)
    outp = Path(args.out)
    spec = json.loads(inp.read_text(encoding="utf-8"))

    cfgs: Dict[str, str] = dict(spec.get("configs", {}))
    pkg = _parse_package(cfgs.get("package.json", ""))
    deps = dict(pkg.get("dependencies", {}))
    dev_deps = dict(pkg.get("devDependencies", {}))
    scripts = dict(pkg.get("scripts", {}))

    pages = list(spec.get("pages", []))
    comps = list(spec.get("components", []))

    # versions
    versions = {
        "node": None,
        "next": deps.get("next"),
        "typescript": deps.get("typescript"),
        "react": deps.get("react"),
    }

    # routes and layout
    routes = _derive_routes(pages)
    layout_preview = next((it.get("preview", "") for it in pages if str(it.get("path", "")).endswith("layout.tsx")), "")
    layout = {
        "file": next((it.get("path") for it in pages if str(it.get("path", "")).endswith("layout.tsx")), None),
        "providers": _detect_providers(layout_preview),
        "uses_globals_css": ("globals.css" in layout_preview),
    }

    # styling
    styling = {
        "css_framework": "tailwind" if "tailwindcss" in deps else None,
        "tailwind_present": ("tailwindcss" in deps),
        "globals_css": "src/app/globals.css" if "globals.css" in layout_preview else None,
    }

    # graphics
    graphics = {
        "uses_pixi": any(pkg_name in deps for pkg_name in ("pixi.js", "@pixi/react", "pixi-viewport")),
        "pixi_packages": [k for k in deps.keys() if k.startswith("@pixi") or k.startswith("pixi")],
    }

    # characters & tiles (heuristic placeholders)
    characters = {
        "component": next((it.get("path") for it in comps if str(it.get("path", "")).endswith("Character.tsx")), None),
        "props_required": ["textureUrl", "spritesheetData", "x", "y", "orientation"],
        "states": ["isMoving", "isThinking", "isSpeaking"],
    }
    assets = list(spec.get("assets", []))
    tile_assets = [a for a in assets if str(a.get("path", "")).endswith(("tileset.png", "tile.png", "tiles.png")) or "tile" in str(a.get("path", "")).lower()]
    tiles = {
        "tile_set_images": [a.get("path") for a in tile_assets],
        "tile_dim": None,
        "tile_set_dim": None,
        "notes": "Fill tile_dim and tile_set_dim by inspecting image metadata or code constants",
    }

    # env schema
    previews = [it.get("preview", "") for it in pages] + [it.get("preview", "") for it in comps]
    env_vars = _detect_env(previews)
    env_schema = [{"name": n, "required": True} for n in env_vars]

    # allowlists (defaults; adjust later)
    allowlists = {
        "imports": {
            "pages": ["react", "next/*", "@/components/*"],
            "components": ["react", "@pixi/*", "pixi.js", "clsx", "convex/*"],
            "api": ["next/*"],
        },
        "file_write": ["package.json", "tsconfig.json", "next.config.*", "src/**/*", "public/**/*"],
    }

    # verify plan
    verify_commands = [
        "tsc --noEmit",
        "eslint . || true",  # allow missing eslint config
        "echo 'build check placeholder'"
    ]

    spec.update({
        "versions": versions,
        "scripts": scripts,
        "routes": routes,
        "layout": layout,
        "styling": styling,
        "graphics": graphics,
        "characters": characters,
        "tiles": tiles,
        "allowlists": allowlists,
        "env_schema": env_schema,
        "verify_commands": verify_commands,
    })

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

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


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Enrich apprepo spec with routes, versions, layout, styling, env, verify plan")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--inp", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.json"))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.enriched.json"))
    return ap.parse_args()

def _parse_package(pkg_text: str) -> Dict[str, Any]:
    try:
        return json.loads(pkg_text)
    except Exception:
        return {}

def _derive_routes(pages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    routes: List[Dict[str, Any]] = []
    for it in pages or []:
        p = str(it.get("path", ""))
        route = None
        if "/app/" in p:
            try:
                sub = p.split("/app/")[1]
                # remove trailing file name
                sub = sub.replace("/page.tsx", "").rstrip("/")
                route = "/" + sub


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Enrich apprepo spec with routes, versions, layout, styling, env, verify plan")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--inp", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.json"))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.enriched.json"))
    return ap.parse_args()

def _parse_package(pkg_text: str) -> Dict[str, Any]:
    try:
        return json.loads(pkg_text)
    except Exception:
        return {}

def _derive_routes(pages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    routes: List[Dict[str, Any]] = []
    for it in pages or []:
        p = str(it.get("path", ""))
        route = None
        if "/app/" in p:
            try:
                sub = p.split("/app/")[1]
                # remove trailing file name
                sub = sub.replace("/page.tsx", "").rstrip("/")
                route = "/" + sub
                if route == "/":
                    route = "/"
            except Exception:
                route = None
        routes.append({"file": p, "path": route})
    return routes


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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Enrich apprepo spec with routes, versions, layout, styling, env, verify plan")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--inp", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.json"))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.enriched.json"))
    return ap.parse_args()

def _parse_package(pkg_text: str) -> Dict[str, Any]:
    try:
        return json.loads(pkg_text)
    except Exception:
        return {}

def _derive_routes(pages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    routes: List[Dict[str, Any]] = []
    for it in pages or []:
        p = str(it.get("path", ""))
        route = None
        if "/app/" in p:
            try:
                sub = p.split("/app/")[1]
                # remove trailing file name
                sub = sub.replace("/page.tsx", "").rstrip("/")
                route = "/" + sub
                if route == "/":
                    route = "/"
            except Exception:
                route = None
        routes.append({"file": p, "path": route})
    return routes

def _detect_providers(layout_preview: str) -> List[str]:
    providers: List[str] = []
    for name in ("ClerkProvider", "ConvexProvider", "ConvexProviderWithClerk"):
        if name in layout_preview:
            providers.append(name)
    return providers

def _detect_env(previews: List[str]) -> List[str]:
    envs: set[str] = set()
    pat = re.compile(r"process\.env\.([A-Z0-9_]+)")
    for text in previews:
        for m in pat.findall(text or ""):
            envs.add(m)
    return sorted(envs)

def main() -> int:


def _derive_routes(pages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    routes: List[Dict[str, Any]] = []
    for it in pages or []:
        p = str(it.get("path", ""))
        route = None
        if "/app/" in p:
            try:
                sub = p.split("/app/")[1]
                # remove trailing file name
                sub = sub.replace("/page.tsx", "").rstrip("/")
                route = "/" + sub
                if route == "/":
                    route = "/"
            except Exception:
                route = None
        routes.append({"file": p, "path": route})
    return routes

def _detect_providers(layout_preview: str) -> List[str]:
    providers: List[str] = []
    for name in ("ClerkProvider", "ConvexProvider", "ConvexProviderWithClerk"):
        if name in layout_preview:
            providers.append(name)
    return providers

def _detect_env(previews: List[str]) -> List[str]:
    envs: set[str] = set()
    pat = re.compile(r"process\.env\.([A-Z0-9_]+)")
    for text in previews:
        for m in pat.findall(text or ""):
            envs.add(m)
    return sorted(envs)

def main() -> int:
    args = parse_args()
    inp = Path(args.inp)
    outp = Path(args.out)
    spec = json.loads(inp.read_text(encoding="utf-8"))

    cfgs: Dict[str, str] = dict(spec.get("configs", {}))
    pkg = _parse_package(cfgs.get("package.json", ""))
    deps = dict(pkg.get("dependencies", {}))


                sub = p.split("/app/")[1]
                # remove trailing file name
                sub = sub.replace("/page.tsx", "").rstrip("/")
                route = "/" + sub
                if route == "/":
                    route = "/"
            except Exception:
                route = None
        routes.append({"file": p, "path": route})
    return routes

def _detect_providers(layout_preview: str) -> List[str]:
    providers: List[str] = []
    for name in ("ClerkProvider", "ConvexProvider", "ConvexProviderWithClerk"):
        if name in layout_preview:
            providers.append(name)
    return providers

def _detect_env(previews: List[str]) -> List[str]:
    envs: set[str] = set()
    pat = re.compile(r"process\.env\.([A-Z0-9_]+)")
    for text in previews:
        for m in pat.findall(text or ""):
            envs.add(m)
    return sorted(envs)

def main() -> int:
    args = parse_args()
    inp = Path(args.inp)
    outp = Path(args.out)
    spec = json.loads(inp.read_text(encoding="utf-8"))

    cfgs: Dict[str, str] = dict(spec.get("configs", {}))
    pkg = _parse_package(cfgs.get("package.json", ""))
    deps = dict(pkg.get("dependencies", {}))
    dev_deps = dict(pkg.get("devDependencies", {}))
    scripts = dict(pkg.get("scripts", {}))

    pages = list(spec.get("pages", []))
    comps = list(spec.get("components", []))

    # versions
    versions = {
        "node": None,


    return routes

def _detect_providers(layout_preview: str) -> List[str]:
    providers: List[str] = []
    for name in ("ClerkProvider", "ConvexProvider", "ConvexProviderWithClerk"):
        if name in layout_preview:
            providers.append(name)
    return providers

def _detect_env(previews: List[str]) -> List[str]:
    envs: set[str] = set()
    pat = re.compile(r"process\.env\.([A-Z0-9_]+)")
    for text in previews:
        for m in pat.findall(text or ""):
            envs.add(m)
    return sorted(envs)

def main() -> int:
    args = parse_args()
    inp = Path(args.inp)
    outp = Path(args.out)
    spec = json.loads(inp.read_text(encoding="utf-8"))

    cfgs: Dict[str, str] = dict(spec.get("configs", {}))
    pkg = _parse_package(cfgs.get("package.json", ""))
    deps = dict(pkg.get("dependencies", {}))
    dev_deps = dict(pkg.get("devDependencies", {}))
    scripts = dict(pkg.get("scripts", {}))

    pages = list(spec.get("pages", []))
    comps = list(spec.get("components", []))

    # versions
    versions = {
        "node": None,
        "next": deps.get("next"),
        "typescript": deps.get("typescript"),
        "react": deps.get("react"),
    }

    # routes and layout
    routes = _derive_routes(pages)
    layout_preview = next((it.get("preview", "") for it in pages if str(it.get("path", "")).endswith("layout.tsx")), "")
    layout = {
        "file": next((it.get("path") for it in pages if str(it.get("path", "")).endswith("layout.tsx")), None),
        "providers": _detect_providers(layout_preview),
        "uses_globals_css": ("globals.css" in layout_preview),
    }

    # styling
    styling = {
        "css_framework": "tailwind" if "tailwindcss" in deps else None,
        "tailwind_present": ("tailwindcss" in deps),
        "globals_css": "src/app/globals.css" if "globals.css" in layout_preview else None,
    }

    # graphics
    graphics = {
        "uses_pixi": any(pkg_name in deps for pkg_name in ("pixi.js", "@pixi/react", "pixi-viewport")),
        "pixi_packages": [k for k in deps.keys() if k.startswith("@pixi") or k.startswith("pixi")],
    }

    # characters & tiles (heuristic placeholders)
    characters = {
        "component": next((it.get("path") for it in comps if str(it.get("path", "")).endswith("Character.tsx")), None),
        "props_required": ["textureUrl", "spritesheetData", "x", "y", "orientation"],
        "states": ["isMoving", "isThinking", "isSpeaking"],
    }
    assets = list(spec.get("assets", []))
    tile_assets = [a for a in assets if str(a.get("path", "")).endswith(("tileset.png", "tile.png", "tiles.png")) or "tile" in str(a.get("path", "")).lower()]
    tiles = {
        "tile_set_images": [a.get("path") for a in tile_assets],
        "tile_dim": None,
        "tile_set_dim": None,
        "notes": "Fill tile_dim and tile_set_dim by inspecting image metadata or code constants",
    }

    # env schema
    previews = [it.get("preview", "") for it in pages] + [it.get("preview", "") for it in comps]
    env_vars = _detect_env(previews)
    env_schema = [{"name": n, "required": True} for n in env_vars]

    # allowlists (defaults; adjust later)
    allowlists = {
        "imports": {
            "pages": ["react", "next/*", "@/components/*"],
            "components": ["react", "@pixi/*", "pixi.js", "clsx", "convex/*"],
            "api": ["next/*"],
        },
        "file_write": ["package.json", "tsconfig.json", "next.config.*", "src/**/*", "public/**/*"],
    }

    # verify plan
    verify_commands = [
        "tsc --noEmit",
        "eslint . || true",  # allow missing eslint config
        "echo 'build check placeholder'"
    ]

    spec.update({
        "versions": versions,
        "scripts": scripts,
        "routes": routes,
        "layout": layout,
        "styling": styling,
        "graphics": graphics,
        "characters": characters,
        "tiles": tiles,
        "allowlists": allowlists,
        "env_schema": env_schema,
        "verify_commands": verify_commands,
    })

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

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


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Extract a lightweight app repo spec from inspiration/ai-town")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/ai-town")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.json"))
    return ap.parse_args()

def _read_text(p: Path) -> str:
    try:
        return p.read_text(encoding="utf-8")
    except Exception:
        return ""

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    spec: Dict[str, Any] = {
        "name": "ai-town-like",
        "framework": "nextjs",
        "language": "ts",
        "pages": [],
        "components": [],
        "assets": [],
        "configs": {}
    }

    # Pages
    pages_dir = src / "src" / "app"
    if pages_dir.exists():
        for p in pages_dir.rglob("*.tsx"):
            rel = p.relative_to(src).as_posix()
            spec["pages"].append({"path": rel, "preview": _read_text(p)[:800]})

    # Components
    comp_dir = src / "src" / "components"
    if comp_dir.exists():
        for p in comp_dir.rglob("*.tsx"):
            rel = p.relative_to(src).as_posix()
            spec["components"].append({"path": rel, "preview": _read_text(p)[:800]})

    # Assets
    assets_dir = src / "public"
    if assets_dir.exists():
        for p in assets_dir.rglob("*"):
            if p.is_file() and p.suffix.lower() in (".svg", ".png", ".jpg", ".jpeg"):
                rel = p.relative_to(src).as_posix()
                spec["assets"].append({"path": rel})

    # Configs (package.json, tsconfig, next.config)
    for fname in ("package.json", "tsconfig.json", "next.config.js", "next.config.mjs", "eslint.config.js", "eslint.config.mjs"):
        fp = src / fname
        if fp.exists():
            spec["configs"][fname] = _read_text(fp)[:4000]

    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)
    outp.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(outp), "pages": len(spec["pages"]), "components": len(spec["components"]), "assets": len(spec["assets"]) }))
    return 0

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


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Extract a lightweight app repo spec from inspiration/ai-town")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/ai-town")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.json"))
    return ap.parse_args()

def _read_text(p: Path) -> str:
    try:
        return p.read_text(encoding="utf-8")
    except Exception:
        return ""

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    spec: Dict[str, Any] = {
        "name": "ai-town-like",
        "framework": "nextjs",
        "language": "ts",
        "pages": [],
        "components": [],
        "assets": [],
        "configs": {}


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Extract a lightweight app repo spec from inspiration/ai-town")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/ai-town")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.json"))
    return ap.parse_args()

def _read_text(p: Path) -> str:
    try:
        return p.read_text(encoding="utf-8")
    except Exception:
        return ""

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    spec: Dict[str, Any] = {
        "name": "ai-town-like",
        "framework": "nextjs",
        "language": "ts",
        "pages": [],
        "components": [],
        "assets": [],
        "configs": {}
    }

    # Pages
    pages_dir = src / "src" / "app"
    if pages_dir.exists():
        for p in pages_dir.rglob("*.tsx"):
            rel = p.relative_to(src).as_posix()


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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Extract a lightweight app repo spec from inspiration/ai-town")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/ai-town")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.json"))
    return ap.parse_args()

def _read_text(p: Path) -> str:
    try:
        return p.read_text(encoding="utf-8")
    except Exception:
        return ""

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    spec: Dict[str, Any] = {
        "name": "ai-town-like",
        "framework": "nextjs",
        "language": "ts",
        "pages": [],
        "components": [],
        "assets": [],
        "configs": {}
    }

    # Pages
    pages_dir = src / "src" / "app"
    if pages_dir.exists():
        for p in pages_dir.rglob("*.tsx"):
            rel = p.relative_to(src).as_posix()
            spec["pages"].append({"path": rel, "preview": _read_text(p)[:800]})

    # Components
    comp_dir = src / "src" / "components"
    if comp_dir.exists():
        for p in comp_dir.rglob("*.tsx"):
            rel = p.relative_to(src).as_posix()
            spec["components"].append({"path": rel, "preview": _read_text(p)[:800]})

    # Assets
    assets_dir = src / "public"
    if assets_dir.exists():
        for p in assets_dir.rglob("*"):
            if p.is_file() and p.suffix.lower() in (".svg", ".png", ".jpg", ".jpeg"):
                rel = p.relative_to(src).as_posix()
                spec["assets"].append({"path": rel})

    # Configs (package.json, tsconfig, next.config)
    for fname in ("package.json", "tsconfig.json", "next.config.js", "next.config.mjs", "eslint.config.js", "eslint.config.mjs"):
        fp = src / fname
        if fp.exists():
            spec["configs"][fname] = _read_text(fp)[:4000]

    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)
    outp.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(outp), "pages": len(spec["pages"]), "components": len(spec["components"]), "assets": len(spec["assets"]) }))
    return 0

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


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Agnostic multi-step spec task: scan a repo and produce a structured spec")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", required=True, help="Path to source repository root")
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "spec_tasks" / "spec.json"))
    ap.add_argument("--max-readme-bytes", type=int, default=20000)
    ap.add_argument("--include-prompts", action="store_true")
    ap.add_argument("--prompts-dirs", default="prompts,prompt_templates,table_construction_prompts")
    return ap.parse_args()

def step_collect_readme(src: Path, max_bytes: int) -> Dict[str, Any]:
    rd = src / "README.md"
    content = ""
    if rd.exists():
        try:
            data = rd.read_text(encoding="utf-8", errors="ignore")
            content = data if len(data) <= max_bytes else (data[: max_bytes // 2] + "\n...\n" + data[-max_bytes // 2 :])
        except Exception:
            content = ""
    return {"path": rd.relative_to(src).as_posix() if rd.exists() else None, "content": content}

def step_list_tree(src: Path) -> Dict[str, Any]:
    dirs = []
    files = []
    for p in sorted(src.rglob("*")):
        if p.name.startswith("."):
            continue
        rel = p.relative_to(src).as_posix()
        if p.is_dir():
            dirs.append(rel)
        elif p.is_file():
            files.append(rel)
    return {"directories": dirs, "files": files}

def step_collect_prompts(src: Path, prompt_dirs: list[str]) -> list[dict[str, str]]:
    items: list[dict[str, str]] = []
    for d in prompt_dirs:
        pd = src / d
        if not pd.exists() or not pd.is_dir():
            continue
        for f in sorted(pd.rglob("*")):
            if f.is_file() and f.suffix.lower() in (".md", ".txt", ".json", ".yaml", ".yml"):
                try:
                    text = f.read_text(encoding="utf-8", errors="ignore")
                except Exception:
                    text = ""
                items.append({"path": f.relative_to(src).as_posix(), "preview": text[:2000]})
    return items

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)

    spec: Dict[str, Any] = {
        "name": src.name,
        "root": src.as_posix(),
        "readme": step_collect_readme(src, int(args.max_readme_bytes)),
        "tree": step_list_tree(src),
    }
    if bool(getattr(args, "include_prompts", False)):
        pdirs = [s.strip() for s in str(getattr(args, "prompts_dirs", "")).split(",") if s.strip()]
        spec["prompts"] = step_collect_prompts(src, pdirs)

    outp.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(outp), "dirs": len(spec.get("tree", {}).get("directories", [])), "prompts": len(spec.get("prompts", [])) if spec.get("prompts") else 0 }))
    return 0

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


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Agnostic multi-step spec task: scan a repo and produce a structured spec")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", required=True, help="Path to source repository root")
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "spec_tasks" / "spec.json"))
    ap.add_argument("--max-readme-bytes", type=int, default=20000)
    ap.add_argument("--include-prompts", action="store_true")
    ap.add_argument("--prompts-dirs", default="prompts,prompt_templates,table_construction_prompts")
    return ap.parse_args()

def step_collect_readme(src: Path, max_bytes: int) -> Dict[str, Any]:
    rd = src / "README.md"
    content = ""
    if rd.exists():
        try:
            data = rd.read_text(encoding="utf-8", errors="ignore")
            content = data if len(data) <= max_bytes else (data[: max_bytes // 2] + "\n...\n" + data[-max_bytes // 2 :])
        except Exception:
            content = ""
    return {"path": rd.relative_to(src).as_posix() if rd.exists() else None, "content": content}

def step_list_tree(src: Path) -> Dict[str, Any]:
    dirs = []
    files = []
    for p in sorted(src.rglob("*")):
        if p.name.startswith("."):
            continue


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Agnostic multi-step spec task: scan a repo and produce a structured spec")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", required=True, help="Path to source repository root")
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "spec_tasks" / "spec.json"))
    ap.add_argument("--max-readme-bytes", type=int, default=20000)
    ap.add_argument("--include-prompts", action="store_true")
    ap.add_argument("--prompts-dirs", default="prompts,prompt_templates,table_construction_prompts")
    return ap.parse_args()

def step_collect_readme(src: Path, max_bytes: int) -> Dict[str, Any]:
    rd = src / "README.md"
    content = ""
    if rd.exists():
        try:
            data = rd.read_text(encoding="utf-8", errors="ignore")
            content = data if len(data) <= max_bytes else (data[: max_bytes // 2] + "\n...\n" + data[-max_bytes // 2 :])
        except Exception:
            content = ""
    return {"path": rd.relative_to(src).as_posix() if rd.exists() else None, "content": content}

def step_list_tree(src: Path) -> Dict[str, Any]:
    dirs = []
    files = []
    for p in sorted(src.rglob("*")):
        if p.name.startswith("."):
            continue
        rel = p.relative_to(src).as_posix()
        if p.is_dir():
            dirs.append(rel)
        elif p.is_file():
            files.append(rel)
    return {"directories": dirs, "files": files}

def step_collect_prompts(src: Path, prompt_dirs: list[str]) -> list[dict[str, str]]:
    items: list[dict[str, str]] = []
    for d in prompt_dirs:
        pd = src / d


    ap.add_argument("--src", required=True, help="Path to source repository root")
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "spec_tasks" / "spec.json"))
    ap.add_argument("--max-readme-bytes", type=int, default=20000)
    ap.add_argument("--include-prompts", action="store_true")
    ap.add_argument("--prompts-dirs", default="prompts,prompt_templates,table_construction_prompts")
    return ap.parse_args()

def step_collect_readme(src: Path, max_bytes: int) -> Dict[str, Any]:
    rd = src / "README.md"
    content = ""
    if rd.exists():
        try:
            data = rd.read_text(encoding="utf-8", errors="ignore")
            content = data if len(data) <= max_bytes else (data[: max_bytes // 2] + "\n...\n" + data[-max_bytes // 2 :])
        except Exception:
            content = ""
    return {"path": rd.relative_to(src).as_posix() if rd.exists() else None, "content": content}

def step_list_tree(src: Path) -> Dict[str, Any]:
    dirs = []
    files = []
    for p in sorted(src.rglob("*")):
        if p.name.startswith("."):
            continue
        rel = p.relative_to(src).as_posix()
        if p.is_dir():
            dirs.append(rel)
        elif p.is_file():
            files.append(rel)
    return {"directories": dirs, "files": files}

def step_collect_prompts(src: Path, prompt_dirs: list[str]) -> list[dict[str, str]]:
    items: list[dict[str, str]] = []
    for d in prompt_dirs:
        pd = src / d
        if not pd.exists() or not pd.is_dir():
            continue
        for f in sorted(pd.rglob("*")):
            if f.is_file() and f.suffix.lower() in (".md", ".txt", ".json", ".yaml", ".yml"):
                try:
                    text = f.read_text(encoding="utf-8", errors="ignore")
                except Exception:
                    text = ""
                items.append({"path": f.relative_to(src).as_posix(), "preview": text[:2000]})
    return items

def main() -> int:
    args = parse_args()


            content = data if len(data) <= max_bytes else (data[: max_bytes // 2] + "\n...\n" + data[-max_bytes // 2 :])
        except Exception:
            content = ""
    return {"path": rd.relative_to(src).as_posix() if rd.exists() else None, "content": content}

def step_list_tree(src: Path) -> Dict[str, Any]:
    dirs = []
    files = []
    for p in sorted(src.rglob("*")):
        if p.name.startswith("."):
            continue
        rel = p.relative_to(src).as_posix()
        if p.is_dir():
            dirs.append(rel)
        elif p.is_file():
            files.append(rel)
    return {"directories": dirs, "files": files}

def step_collect_prompts(src: Path, prompt_dirs: list[str]) -> list[dict[str, str]]:
    items: list[dict[str, str]] = []
    for d in prompt_dirs:
        pd = src / d
        if not pd.exists() or not pd.is_dir():
            continue
        for f in sorted(pd.rglob("*")):
            if f.is_file() and f.suffix.lower() in (".md", ".txt", ".json", ".yaml", ".yml"):
                try:
                    text = f.read_text(encoding="utf-8", errors="ignore")
                except Exception:
                    text = ""
                items.append({"path": f.relative_to(src).as_posix(), "preview": text[:2000]})
    return items

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)

    spec: Dict[str, Any] = {
        "name": src.name,
        "root": src.as_posix(),
        "readme": step_collect_readme(src, int(args.max_readme_bytes)),
        "tree": step_list_tree(src),
    }
    if bool(getattr(args, "include_prompts", False)):
        pdirs = [s.strip() for s in str(getattr(args, "prompts_dirs", "")).split(",") if s.strip()]
        spec["prompts"] = step_collect_prompts(src, pdirs)

    outp.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(outp), "dirs": len(spec.get("tree", {}).get("directories", [])), "prompts": len(spec.get("prompts", [])) if spec.get("prompts") else 0 }))


            files.append(rel)
    return {"directories": dirs, "files": files}

def step_collect_prompts(src: Path, prompt_dirs: list[str]) -> list[dict[str, str]]:
    items: list[dict[str, str]] = []
    for d in prompt_dirs:
        pd = src / d
        if not pd.exists() or not pd.is_dir():
            continue
        for f in sorted(pd.rglob("*")):
            if f.is_file() and f.suffix.lower() in (".md", ".txt", ".json", ".yaml", ".yml"):
                try:
                    text = f.read_text(encoding="utf-8", errors="ignore")
                except Exception:
                    text = ""
                items.append({"path": f.relative_to(src).as_posix(), "preview": text[:2000]})
    return items

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)

    spec: Dict[str, Any] = {
        "name": src.name,
        "root": src.as_posix(),
        "readme": step_collect_readme(src, int(args.max_readme_bytes)),
        "tree": step_list_tree(src),
    }
    if bool(getattr(args, "include_prompts", False)):
        pdirs = [s.strip() for s in str(getattr(args, "prompts_dirs", "")).split(",") if s.strip()]
        spec["prompts"] = step_collect_prompts(src, pdirs)

    outp.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(outp), "dirs": len(spec.get("tree", {}).get("directories", [])), "prompts": len(spec.get("prompts", [])) if spec.get("prompts") else 0 }))
    return 0

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


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Build a generic stack spec from a source repo (README + directories + prompts)")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/llm-app-stack")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "stack" / "spec.llm_app_stack.json"))
    ap.add_argument("--readme-max-bytes", type=int, default=20000)
    return ap.parse_args()

def _read_text(p: Path, max_bytes: int | None = None) -> str:
    try:
        data = p.read_text(encoding="utf-8", errors="ignore")
        if max_bytes is not None and len(data.encode("utf-8", errors="ignore")) > max_bytes:
            # Rough truncate on codepoint boundary
            return data[: max_bytes // 2] + "\n...\n" + data[-max_bytes // 2 :]
        return data
    except Exception:
        return ""

def _parse_readme_sections(text: str) -> List[Dict[str, str]]:
    if not text:
        return []
    sections: List[Dict[str, str]] = []
    # Split on Markdown headings (#, ##, ###)
    parts = re.split(r"(^#{1,6}\s+.+$)", text, flags=re.MULTILINE)
    if len(parts) == 1:
        return [{"title": "README", "content": text}]
    pending_title = None
    buf = []
    for part in parts:
        if re.match(r"^#{1,6}\s+.+$", part or ""):
            if pending_title is not None:
                sections.append({"title": pending_title.strip(), "content": "".join(buf).strip()})
                buf = []
            pending_title = re.sub(r"^#{1,6}\s+", "", part).strip()
        else:
            buf.append(part)
    if pending_title is not None:
        sections.append({"title": pending_title.strip(), "content": "".join(buf).strip()})
    return sections

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)

    spec: Dict[str, Any] = {
        "name": src.name,
        "root": src.as_posix(),
        "readme": {},
        "directories": [],
        "files": [],
        "prompts": [],
    }

    # README
    readme_path = src / "README.md"
    if readme_path.exists():
        text = _read_text(readme_path, max_bytes=int(args.readme_max_bytes))
        spec["readme"] = {
            "path": readme_path.relative_to(src).as_posix(),
            "sections": _parse_readme_sections(text),
        }

    # Top-level directories and files (small listing)
    for p in sorted(src.iterdir()):
        if p.name.startswith("."):
            continue
        if p.is_dir():
            spec["directories"].append(p.name)
        elif p.is_file():
            spec["files"].append(p.name)

    # Prompt directories
    for prompt_dir_name in ("prompts", "prompt_templates", "table_construction_prompts"):
        pd = src / prompt_dir_name
        if pd.exists() and pd.is_dir():
            for f in sorted(pd.rglob("*")):
                if f.is_file() and f.suffix.lower() in (".md", ".txt", ".json", ".yaml", ".yml"):
                    spec["prompts"].append({
                        "path": f.relative_to(src).as_posix(),
                        "preview": _read_text(f)[:2000],
                    })

    outp.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(outp), "dirs": len(spec["directories"]), "prompts": len(spec["prompts"]) }))
    return 0

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


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Build a generic stack spec from a source repo (README + directories + prompts)")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/llm-app-stack")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "stack" / "spec.llm_app_stack.json"))
    ap.add_argument("--readme-max-bytes", type=int, default=20000)
    return ap.parse_args()

def _read_text(p: Path, max_bytes: int | None = None) -> str:
    try:
        data = p.read_text(encoding="utf-8", errors="ignore")
        if max_bytes is not None and len(data.encode("utf-8", errors="ignore")) > max_bytes:
            # Rough truncate on codepoint boundary
            return data[: max_bytes // 2] + "\n...\n" + data[-max_bytes // 2 :]
        return data
    except Exception:
        return ""

def _parse_readme_sections(text: str) -> List[Dict[str, str]]:
    if not text:
        return []
    sections: List[Dict[str, str]] = []
    # Split on Markdown headings (#, ##, ###)
    parts = re.split(r"(^#{1,6}\s+.+$)", text, flags=re.MULTILINE)
    if len(parts) == 1:


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Build a generic stack spec from a source repo (README + directories + prompts)")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/llm-app-stack")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "stack" / "spec.llm_app_stack.json"))
    ap.add_argument("--readme-max-bytes", type=int, default=20000)
    return ap.parse_args()

def _read_text(p: Path, max_bytes: int | None = None) -> str:
    try:
        data = p.read_text(encoding="utf-8", errors="ignore")
        if max_bytes is not None and len(data.encode("utf-8", errors="ignore")) > max_bytes:
            # Rough truncate on codepoint boundary
            return data[: max_bytes // 2] + "\n...\n" + data[-max_bytes // 2 :]
        return data
    except Exception:
        return ""

def _parse_readme_sections(text: str) -> List[Dict[str, str]]:
    if not text:
        return []
    sections: List[Dict[str, str]] = []
    # Split on Markdown headings (#, ##, ###)
    parts = re.split(r"(^#{1,6}\s+.+$)", text, flags=re.MULTILINE)
    if len(parts) == 1:
        return [{"title": "README", "content": text}]
    pending_title = None
    buf = []
    for part in parts:
        if re.match(r"^#{1,6}\s+.+$", part or ""):
            if pending_title is not None:
                sections.append({"title": pending_title.strip(), "content": "".join(buf).strip()})
                buf = []
            pending_title = re.sub(r"^#{1,6}\s+", "", part).strip()
        else:
            buf.append(part)


def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Build a generic stack spec from a source repo (README + directories + prompts)")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/llm-app-stack")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "stack" / "spec.llm_app_stack.json"))
    ap.add_argument("--readme-max-bytes", type=int, default=20000)
    return ap.parse_args()

def _read_text(p: Path, max_bytes: int | None = None) -> str:
    try:
        data = p.read_text(encoding="utf-8", errors="ignore")
        if max_bytes is not None and len(data.encode("utf-8", errors="ignore")) > max_bytes:
            # Rough truncate on codepoint boundary
            return data[: max_bytes // 2] + "\n...\n" + data[-max_bytes // 2 :]
        return data
    except Exception:
        return ""

def _parse_readme_sections(text: str) -> List[Dict[str, str]]:
    if not text:
        return []
    sections: List[Dict[str, str]] = []
    # Split on Markdown headings (#, ##, ###)
    parts = re.split(r"(^#{1,6}\s+.+$)", text, flags=re.MULTILINE)
    if len(parts) == 1:
        return [{"title": "README", "content": text}]
    pending_title = None
    buf = []
    for part in parts:
        if re.match(r"^#{1,6}\s+.+$", part or ""):
            if pending_title is not None:
                sections.append({"title": pending_title.strip(), "content": "".join(buf).strip()})
                buf = []
            pending_title = re.sub(r"^#{1,6}\s+", "", part).strip()
        else:
            buf.append(part)
    if pending_title is not None:
        sections.append({"title": pending_title.strip(), "content": "".join(buf).strip()})
    return sections

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)

    spec: Dict[str, Any] = {
        "name": src.name,
        "root": src.as_posix(),
        "readme": {},
        "directories": [],
        "files": [],
        "prompts": [],
    }

    # README
    readme_path = src / "README.md"
    if readme_path.exists():


    sections: List[Dict[str, str]] = []
    # Split on Markdown headings (#, ##, ###)
    parts = re.split(r"(^#{1,6}\s+.+$)", text, flags=re.MULTILINE)
    if len(parts) == 1:
        return [{"title": "README", "content": text}]
    pending_title = None
    buf = []
    for part in parts:
        if re.match(r"^#{1,6}\s+.+$", part or ""):
            if pending_title is not None:
                sections.append({"title": pending_title.strip(), "content": "".join(buf).strip()})
                buf = []
            pending_title = re.sub(r"^#{1,6}\s+", "", part).strip()
        else:
            buf.append(part)
    if pending_title is not None:
        sections.append({"title": pending_title.strip(), "content": "".join(buf).strip()})
    return sections

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    outp = Path(args.out)
    outp.parent.mkdir(parents=True, exist_ok=True)

    spec: Dict[str, Any] = {
        "name": src.name,
        "root": src.as_posix(),
        "readme": {},
        "directories": [],
        "files": [],
        "prompts": [],
    }

    # README
    readme_path = src / "README.md"
    if readme_path.exists():
        text = _read_text(readme_path, max_bytes=int(args.readme_max_bytes))
        spec["readme"] = {
            "path": readme_path.relative_to(src).as_posix(),
            "sections": _parse_readme_sections(text),
        }

    # Top-level directories and files (small listing)
    for p in sorted(src.iterdir()):
        if p.name.startswith("."):
            continue
        if p.is_dir():
            spec["directories"].append(p.name)
        elif p.is_file():
            spec["files"].append(p.name)

    # Prompt directories
    for prompt_dir_name in ("prompts", "prompt_templates", "table_construction_prompts"):
        pd = src / prompt_dir_name
        if pd.exists() and pd.is_dir():
            for f in sorted(pd.rglob("*")):
                if f.is_file() and f.suffix.lower() in (".md", ".txt", ".json", ".yaml", ".yml"):
                    spec["prompts"].append({
                        "path": f.relative_to(src).as_posix(),
                        "preview": _read_text(f)[:2000],
                    })

    outp.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(outp), "dirs": len(spec["directories"]), "prompts": len(spec["prompts"]) }))
    return 0

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


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Build a refined Next.js apprepo spec from a source repo (extract + enrich)")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/ai-town")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.refined.json"))
    return ap.parse_args()

def _read_text(p: Path) -> str:
    try:
        return p.read_text(encoding="utf-8")
    except Exception:
        return ""

def _extract(src: Path) -> Dict[str, Any]:
    spec: Dict[str, Any] = {
        "name": "ai-town-like",
        "framework": "nextjs",
        "language": "ts",
        "pages": [],
        "components": [],
        "assets": [],
        "configs": {}
    }
    pages_dir = src / "src" / "app"
    if pages_dir.exists():
        for p in pages_dir.rglob("*.tsx"):
            rel = p.relative_to(src).as_posix()
            spec["pages"].append({"path": rel, "preview": _read_text(p)[:800]})
    comp_dir = src / "src" / "components"
    if comp_dir.exists():
        for p in comp_dir.rglob("*.tsx"):
            rel = p.relative_to(src).as_posix()
            spec["components"].append({"path": rel, "preview": _read_text(p)[:800]})
    assets_dir = src / "public"
    if assets_dir.exists():
        for p in assets_dir.rglob("*"):
            if p.is_file() and p.suffix.lower() in (".svg", ".png", ".jpg", ".jpeg"):
                rel = p.relative_to(src).as_posix()
                spec["assets"].append({"path": rel})
    for fname in ("package.json", "tsconfig.json", "next.config.js", "next.config.mjs", "eslint.config.js", "eslint.config.mjs", "tailwind.config.js", "tailwind.config.cjs", "tailwind.config.ts", "postcss.config.js", "postcss.config.cjs"):
        fp = src / fname
        if fp.exists():
            spec["configs"][fname] = _read_text(fp)[:4000]
    # Globals CSS
    gcss = src / "src" / "app" / "globals.css"
    if gcss.exists():
        spec.setdefault("configs", {})["globals.css"] = _read_text(gcss)[:4000]
    # Fonts directory
    fonts_dir = src / "public" / "assets" / "fonts"
    if fonts_dir.exists():
        fonts = []
        for p in fonts_dir.rglob("*"):
            if p.is_file():
                fonts.append({"path": p.relative_to(src).as_posix()})
        if fonts:
            spec["assets"].extend(fonts)
    return spec

def _parse_package(pkg_text: str) -> Dict[str, Any]:
    try:
        return json.loads(pkg_text)
    except Exception:
        return {}

def _derive_routes(pages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    routes: List[Dict[str, Any]] = []
    for it in pages or []:
        p = str(it.get("path", ""))
        # Skip layout and non-page utility files in routes
        if p.endswith("layout.tsx") or not p.endswith("page.tsx"):
            routes.append({"file": p, "path": None})
            continue
        route = None
        try:
            sub = p.split("/app/")[1]
            sub = sub[:-len("/page.tsx")] if sub.endswith("/page.tsx") else sub
            if sub == "":
                route = "/"
            else:
                route = "/" + sub.strip("/")
        except Exception:
            route = None
        routes.append({"file": p, "path": route})
    return routes

def _detect_providers(layout_preview: str) -> List[str]:
    providers: List[str] = []
    for name in ("ClerkProvider", "ConvexProvider", "ConvexProviderWithClerk", "ConvexClientProvider"):
        if name in layout_preview:
            providers.append(name)
    return providers

def _detect_env(previews: List[str]) -> List[str]:
    envs: set[str] = set()
    pat = re.compile(r"process\.env\.([A-Z0-9_]+)")
    for text in previews:
        for m in pat.findall(text or ""):
            envs.add(m)
    return sorted(envs)

def _enrich(spec: Dict[str, Any], src_root: Path) -> Dict[str, Any]:
    cfgs: Dict[str, str] = dict(spec.get("configs", {}))
    pkg = _parse_package(cfgs.get("package.json", ""))
    deps = dict(pkg.get("dependencies", {}))
    scripts = dict(pkg.get("scripts", {}))

    pages = list(spec.get("pages", []))
    comps = list(spec.get("components", []))
    versions = {
        "node": None,
        "next": deps.get("next"),
        "typescript": deps.get("typescript"),
        "react": deps.get("react"),
    }
    # Try to read Node version from package.json engines
    try:
        eng = dict(_parse_package(cfgs.get("package.json", "")).get("engines", {}))
        if isinstance(eng.get("node"), str):
            versions["node"] = eng.get("node")
    except Exception:
        pass
    routes = _derive_routes(pages)
    layout_preview = next((it.get("preview", "") for it in pages if str(it.get("path", "")).endswith("layout.tsx")), "")
    layout = {
        "file": next((it.get("path") for it in pages if str(it.get("path", "")).endswith("layout.tsx")), None),
        "providers": _detect_providers(layout_preview),
        "uses_globals_css": ("globals.css" in layout_preview),
    }
    styling = {
        "css_framework": "tailwind" if "tailwindcss" in deps else None,
        "tailwind_present": ("tailwindcss" in deps),
        "globals_css": "src/app/globals.css" if "globals.css" in layout_preview else None,
    }
    graphics = {
        "uses_pixi": any(pkg_name in deps for pkg_name in ("pixi.js", "@pixi/react", "pixi-viewport")),
        "pixi_packages": [k for k in deps.keys() if k.startswith("@pixi") or k.startswith("pixi")],
    }
    characters = {
        "component": next((it.get("path") for it in comps if str(it.get("path", "")).endswith("Character.tsx")), None),
        "props_required": ["textureUrl", "spritesheetData", "x", "y", "orientation"],
        "states": ["isMoving", "isThinking", "isSpeaking"],
    }
    assets = list(spec.get("assets", []))
    tile_assets = [a for a in assets if str(a.get("path", "")).endswith(("tileset.png", "tile.png", "tiles.png")) or "tile" in str(a.get("path", "")).lower()]
    tiles = {
        "tile_set_images": [a.get("path") for a in tile_assets],
        "tile_dim": None,
        "tile_set_dim": None,
        "notes": "Fill tile_dim and tile_set_dim by inspecting image metadata or code constants",
    }
    previews = [it.get("preview", "") for it in pages] + [it.get("preview", "") for it in comps]
    env_vars = _detect_env(previews)
    # Heuristic additions based on deps
    deps_present = set(deps.keys())
    if "@clerk/nextjs" in deps_present:
        for k in ("NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY", "CLERK_SECRET_KEY"):
            if k not in env_vars:
                env_vars.append(k)
    if "@pinecone-database/pinecone" in deps_present:
        for k in ("PINECONE_API_KEY", "PINECONE_ENVIRONMENT"):
            if k not in env_vars:
                env_vars.append(k)
    if "replicate" in deps_present:
        if "REPLICATE_API_TOKEN" not in env_vars:
            env_vars.append("REPLICATE_API_TOKEN")
    env_schema = [{"name": n, "required": True} for n in env_vars]
    allowlists = {
        "imports": {
            "pages": ["react", "next/*", "@/components/*"],
            "components": ["react", "@pixi/*", "pixi.js", "clsx", "convex/*"],
            "api": ["next/*"],
        },
        "file_write": ["package.json", "tsconfig.json", "next.config.*", "src/**/*", "public/**/*"],
    }
    verify_commands = [
        "tsc --noEmit",
        "next lint || true",
        "next build --no-telemetry --no-lint || true"
    ]
    verify_timeouts = {
        "tsc --noEmit": 60,
        "next lint || true": 45,
        "next build --no-telemetry --no-lint || true": 120
    }
    # Generation plan order
    plan = {
        "order": ["configs", "package", "styling", "layout", "routes", "components", "assets", "backend"],
        "repair_checks": ["syntax", "typecheck", "lint", "build"]
    }

    # Backend (Convex) metadata — scan relative to provided --src
    backend: Dict[str, Any] = {}
    cdir = src_root / "convex"
    if cdir.exists():
        backend["schema_file"] = (cdir / "schema.ts").as_posix() if (cdir / "schema.ts").exists() else None
        fn_files = []
        for p in cdir.rglob("*.ts"):
            if "_generated" in p.parts:
                continue
            fn_files.append(p.as_posix())
        backend["functions"] = fn_files
    spec.update({
        "versions": versions,
        "scripts": scripts,
        "routes": routes,
        "layout": layout,
        "styling": styling,
        "graphics": graphics,
        "characters": characters,
        "tiles": tiles,
        "allowlists": allowlists,
        "env_schema": env_schema,
        "verify_commands": verify_commands,
        "verify_timeouts": verify_timeouts,
        "plan": plan,
        "backend": backend,
    })
    return spec

def main() -> int:
    args = parse_args()
    src = Path(args.src)
    outp = Path(args.out)
    base = _extract(src)
    refined = _enrich(base, src)
    outp.parent.mkdir(parents=True, exist_ok=True)
    outp.write_text(json.dumps(refined, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(outp), "routes": len(refined.get("routes", [])), "env": len(refined.get("env_schema", []))}))
    return 0

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


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Build a refined Next.js apprepo spec from a source repo (extract + enrich)")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/ai-town")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.refined.json"))
    return ap.parse_args()

def _read_text(p: Path) -> str:
    try:
        return p.read_text(encoding="utf-8")
    except Exception:
        return ""

def _extract(src: Path) -> Dict[str, Any]:
    spec: Dict[str, Any] = {
        "name": "ai-town-like",
        "framework": "nextjs",
        "language": "ts",
        "pages": [],
        "components": [],
        "assets": [],
        "configs": {}
    }
    pages_dir = src / "src" / "app"


from __future__ import annotations

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

def parse_args() -> Any:
    ap = argparse.ArgumentParser(description="Build a refined Next.js apprepo spec from a source repo (extract + enrich)")
    root = Path(__file__).resolve().parents[2]
    ap.add_argument("--src", default=str(Path("/data/agiattempt/inspiration/ai-town")))
    ap.add_argument("--out", default=str(root / "data" / "tasks" / "apprepo" / "spec.ai_town.refined.json"))
    return ap.parse_args()

def _read_text(p: Path) -> str:
    try:
        return p.read_text(encoding="utf-8")
    except Exception:
        return ""

def _extract(src: Path) -> Dict[str, Any]:
    spec: Dict[str, Any] = {
        "name": "ai-town-like",
        "framework": "nextjs",
        "language": "ts",
        "pages": [],
        "components": [],
        "assets": [],
        "configs": {}
    }
    pages_dir = src / "src" / "app"
    if pages_dir.exists():
        for p in pages_dir.rglob("*.tsx"):
            rel = p.relative_to(src).as_posix()
            spec["pages"].append({"path": rel, "preview": _read_text(p)[:800]})
    comp_dir = src / "src" / "components"
    if comp_dir.exists():
        for p in comp_dir.rglob("*.tsx"):