diff --git a/skillopt_sleep/harvest_sources.py b/skillopt_sleep/harvest_sources.py index e4d89056..c1925531 100644 --- a/skillopt_sleep/harvest_sources.py +++ b/skillopt_sleep/harvest_sources.py @@ -12,6 +12,17 @@ from skillopt_sleep.harvest_pi import harvest_pi from skillopt_sleep.types import SessionDigest +SUPPORTED_SOURCES = ( + "claude", + "codex", + "copilot", + "copilot_cli", + "cursor", + "pi", + "opencode", + "auto", +) + def harvest_for_config( cfg, @@ -26,10 +37,17 @@ def harvest_for_config( `skillopt_sleep.harvest.harvest`); the other sources have no repeat filter to report yet and leave it untouched. """ - source = cfg.get("transcript_source", "claude") + raw_source = cfg.get("transcript_source", "claude") + source = (raw_source or "claude").strip().lower() scope = cfg.get("projects", "invoked") invoked_project = cfg.get("invoked_project", "") + if source not in SUPPORTED_SOURCES: + supported = ", ".join(SUPPORTED_SOURCES) + raise ValueError( + f"Unknown transcript source '{raw_source}'. Supported sources: {supported}" + ) + if source == "codex": return harvest_codex( cfg.codex_archived_sessions_dir, diff --git a/tests/test_sleep_engine.py b/tests/test_sleep_engine.py index bece0e97..ef3b27be 100644 --- a/tests/test_sleep_engine.py +++ b/tests/test_sleep_engine.py @@ -788,6 +788,28 @@ def test_auto_source_keeps_existing_codex_then_claude_precedence(self): cursor_harvest.assert_not_called() + def test_harvest_for_config_rejects_unknown_source(self): + from skillopt_sleep.harvest_sources import SUPPORTED_SOURCES, harvest_for_config + + cfg = load_config(transcript_source="unknown_engine", invoked_project="/repo/project") + with self.assertRaises(ValueError) as ctx: + harvest_for_config(cfg) + + msg = str(ctx.exception) + self.assertIn("Unknown transcript source 'unknown_engine'", msg) + self.assertIn("Supported sources:", msg) + for expected in SUPPORTED_SOURCES: + self.assertIn(expected, msg) + + def test_harvest_for_config_accepts_empty_and_case_insensitive_source(self): + from skillopt_sleep.harvest_sources import harvest_for_config + + expected = [SessionDigest(session_id="claude-session", project="/repo/project")] + for s in ("", "claude", "CLAUDE", "Claude"): + cfg = load_config(transcript_source=s, invoked_project="/repo/project") + with mock.patch("skillopt_sleep.harvest_sources.harvest", return_value=expected): + self.assertEqual(harvest_for_config(cfg), expected) + def test_harvest_codex_filters_project_and_cli_source(self): from skillopt_sleep.__main__ import _cfg_from_args from skillopt_sleep.harvest_sources import harvest_for_config