Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion skillopt_sleep/harvest_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
22 changes: 22 additions & 0 deletions tests/test_sleep_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading