From 3588bbcf941d23ae2c9385264622ae6ea5841b94 Mon Sep 17 00:00:00 2001 From: albertoelopez Date: Wed, 10 Jun 2026 21:33:22 -0700 Subject: [PATCH] fix: explain missing CC2 source bundle --- scripts/generate_cc2_board.py | 26 ++++++++------ tests/test_cc2_board_generator.py | 59 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 tests/test_cc2_board_generator.py diff --git a/scripts/generate_cc2_board.py b/scripts/generate_cc2_board.py index e7aee6d6..7f044a89 100755 --- a/scripts/generate_cc2_board.py +++ b/scripts/generate_cc2_board.py @@ -5,6 +5,7 @@ from __future__ import annotations import argparse import hashlib import json +import os import re import subprocess import sys @@ -93,22 +94,27 @@ def slugify(text: str, limit: int = 54) -> str: return slug[:limit].strip("-") or "item" -def find_source_omx(repo_root: Path) -> Path: - candidates = [] - env = None - try: - import os - env = os.environ.get("CC2_SOURCE_OMX") - except Exception: - env = None +def find_source_omx(repo_root: Path, env_value: str | None = None) -> Path: + candidates: list[Path] = [] + env = env_value if env_value is not None else os.environ.get("CC2_SOURCE_OMX") if env: candidates.append(Path(env).expanduser()) candidates.append(repo_root / ".omx") candidates.extend(parent / ".omx" for parent in repo_root.parents) - for candidate in candidates: + + checked: list[Path] = [] + for candidate in dict.fromkeys(candidate.resolve() for candidate in candidates): + checked.append(candidate) if (candidate / "plans" / "claw-code-2-0-adaptive-plan.md").exists() and (candidate / "research").exists(): return candidate - raise FileNotFoundError("could not locate source .omx with plans/claw-code-2-0-adaptive-plan.md and research/") + + searched = "\n".join(f" - {path}" for path in checked) + raise FileNotFoundError( + "could not locate source .omx with plans/claw-code-2-0-adaptive-plan.md and research/\n" + 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 parse_roadmap(path: Path) -> tuple[list[RoadmapRecord], list[RoadmapRecord]]: diff --git a/tests/test_cc2_board_generator.py b/tests/test_cc2_board_generator.py new file mode 100644 index 00000000..6aa8f066 --- /dev/null +++ b/tests/test_cc2_board_generator.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import importlib.util +import sys +import tempfile +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +GENERATOR = REPO_ROOT / "scripts" / "generate_cc2_board.py" + + +def load_generator(): + spec = importlib.util.spec_from_file_location("generate_cc2_board", GENERATOR) + assert spec and spec.loader + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +class CC2BoardGeneratorTests(unittest.TestCase): + def test_missing_source_omx_error_lists_searched_paths_and_recovery_steps(self) -> None: + generator = load_generator() + with tempfile.TemporaryDirectory() as temp_dir: + repo_root = Path(temp_dir) / "repo" + repo_root.mkdir() + (repo_root / "ROADMAP.md").write_text("# Roadmap\n", encoding="utf-8") + + with self.assertRaises(FileNotFoundError) as caught: + generator.find_source_omx(repo_root) + + message = str(caught.exception) + self.assertIn("could not locate source .omx", message) + self.assertIn("searched:", message) + self.assertIn(str(repo_root / ".omx"), message) + self.assertIn("Set CC2_SOURCE_OMX", message) + self.assertIn("plans/claw-code-2-0-adaptive-plan.md", message) + self.assertIn("research/", message) + + def test_source_omx_can_be_supplied_by_env(self) -> None: + generator = load_generator() + with tempfile.TemporaryDirectory() as temp_dir: + temp_root = Path(temp_dir) + repo_root = temp_root / "repo" + repo_root.mkdir() + source = temp_root / "cc2-source" + (source / "plans").mkdir(parents=True) + (source / "research").mkdir() + (source / "plans" / "claw-code-2-0-adaptive-plan.md").write_text( + "# approved plan\n", encoding="utf-8" + ) + + self.assertEqual(source, generator.find_source_omx(repo_root, env_value=str(source))) + + +if __name__ == "__main__": + unittest.main()