fix: warn before missing CC2 source failure

This commit is contained in:
albertoelopez 2026-06-15 20:22:11 -07:00
parent 4a4d4061f3
commit 99cc652fe1
2 changed files with 43 additions and 10 deletions

View File

@ -12,7 +12,7 @@ import sys
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any, Callable
REQUIRED_ITEM_FIELDS = [ REQUIRED_ITEM_FIELDS = [
"id", "id",
@ -94,26 +94,42 @@ def slugify(text: str, limit: int = 54) -> str:
return slug[:limit].strip("-") or "item" return slug[:limit].strip("-") or "item"
def find_source_omx(repo_root: Path, env_value: str | None = None) -> Path: def source_omx_candidates(repo_root: Path, env_value: str | None = None) -> list[Path]:
candidates: list[Path] = [] candidates: list[Path] = []
env = env_value if env_value is not None else os.environ.get("CC2_SOURCE_OMX") env = env_value if env_value is not None else os.environ.get("CC2_SOURCE_OMX")
if env: if env:
candidates.append(Path(env).expanduser()) candidates.append(Path(env).expanduser())
candidates.append(repo_root / ".omx") candidates.append(repo_root / ".omx")
candidates.extend(parent / ".omx" for parent in repo_root.parents) candidates.extend(parent / ".omx" for parent in repo_root.parents)
return list(dict.fromkeys(candidate.resolve() for candidate in candidates))
def format_source_omx_search(paths: list[Path]) -> str:
searched = "\n".join(f" - {path}" for path in paths)
return (
f"searched:\n{searched}\n"
"Recovery: restore the frozen CC2 source bundle or Set CC2_SOURCE_OMX=/path/to/.omx. "
"The source root must contain plans/claw-code-2-0-adaptive-plan.md and research/."
)
def find_source_omx(
repo_root: Path,
env_value: str | None = None,
warn: Callable[[str], None] | None = None,
) -> Path:
checked: list[Path] = [] checked: list[Path] = []
for candidate in dict.fromkeys(candidate.resolve() for candidate in candidates): for candidate in source_omx_candidates(repo_root, env_value):
checked.append(candidate) checked.append(candidate)
if (candidate / "plans" / "claw-code-2-0-adaptive-plan.md").exists() and (candidate / "research").exists(): if (candidate / "plans" / "claw-code-2-0-adaptive-plan.md").exists() and (candidate / "research").exists():
return candidate return candidate
searched = "\n".join(f" - {path}" for path in checked) search_message = format_source_omx_search(checked)
if warn is not None:
warn(f"warning: searching for CC2 source .omx\n{search_message}")
raise FileNotFoundError( raise FileNotFoundError(
"could not locate source .omx with plans/claw-code-2-0-adaptive-plan.md and research/\n" "could not locate source .omx with plans/claw-code-2-0-adaptive-plan.md and research/\n"
f"searched:\n{searched}\n" f"{search_message}"
"Recovery: restore the frozen CC2 source bundle or Set CC2_SOURCE_OMX=/path/to/.omx. "
"The source root must contain plans/claw-code-2-0-adaptive-plan.md and research/."
) )
@ -425,9 +441,9 @@ def validate_board(board: dict[str, Any]) -> list[str]:
return errors return errors
def build_board(repo_root: Path) -> dict[str, Any]: def build_board(repo_root: Path, warn: Callable[[str], None] | None = None) -> dict[str, Any]:
roadmap_path = repo_root / "ROADMAP.md" roadmap_path = repo_root / "ROADMAP.md"
source_omx = find_source_omx(repo_root) source_omx = find_source_omx(repo_root, warn=warn)
research = source_omx / "research" research = source_omx / "research"
plan_path = source_omx / "plans" / "claw-code-2-0-adaptive-plan.md" plan_path = source_omx / "plans" / "claw-code-2-0-adaptive-plan.md"
headings, actions = parse_roadmap(roadmap_path) headings, actions = parse_roadmap(roadmap_path)
@ -510,7 +526,7 @@ def main() -> int:
repo_root = args.repo_root.resolve() repo_root = args.repo_root.resolve()
out_dir = args.out_dir or (repo_root / ".omx" / "cc2") out_dir = args.out_dir or (repo_root / ".omx" / "cc2")
try: try:
board = build_board(repo_root) board = build_board(repo_root, warn=lambda message: print(message, file=sys.stderr))
except FileNotFoundError as exc: except FileNotFoundError as exc:
print(f"error: {exc}", file=sys.stderr) print(f"error: {exc}", file=sys.stderr)
return 1 return 1

View File

@ -39,6 +39,23 @@ class CC2BoardGeneratorTests(unittest.TestCase):
self.assertIn("plans/claw-code-2-0-adaptive-plan.md", message) self.assertIn("plans/claw-code-2-0-adaptive-plan.md", message)
self.assertIn("research/", message) self.assertIn("research/", message)
def test_missing_source_omx_emits_warning_before_failure(self) -> None:
generator = load_generator()
warnings: list[str] = []
with tempfile.TemporaryDirectory() as temp_dir:
repo_root = Path(temp_dir) / "repo"
repo_root.mkdir()
with self.assertRaises(FileNotFoundError):
generator.find_source_omx(repo_root, warn=warnings.append)
self.assertEqual(1, len(warnings))
warning = warnings[0]
self.assertIn("warning: searching for CC2 source .omx", warning)
self.assertIn("searched:", warning)
self.assertIn(str(repo_root / ".omx"), warning)
self.assertIn("Set CC2_SOURCE_OMX", warning)
def test_source_omx_can_be_supplied_by_env(self) -> None: def test_source_omx_can_be_supplied_by_env(self) -> None:
generator = load_generator() generator = load_generator()
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir: