diff --git a/cheddahbot/scheduler.py b/cheddahbot/scheduler.py index ffa60e1..b472662 100644 --- a/cheddahbot/scheduler.py +++ b/cheddahbot/scheduler.py @@ -435,7 +435,7 @@ class Scheduler: field_mapping = mapping.get("field_mapping", {}) missing_clickup = [field_mapping.get(f, f) for f in missing] msg = f"Skipped: missing required field(s): {', '.join(missing_clickup)}" - log.info("Skipping ClickUp task %s (%s) — %s", task_id, task.name, msg) + log.debug("Skipping ClickUp task %s (%s) — %s", task_id, task.name, msg) self._notify( f"Skipped ClickUp task: **{task.name}**\n{msg}", category="clickup", @@ -453,6 +453,13 @@ class Scheduler: args["clickup_task_id"] = task_id args["clickup_task_status"] = task.status + # Map Work Category to content_type so create_content routes correctly + if tool_name == "create_content": + if task.task_type == "On Page Optimization": + args["content_type"] = "on page optimization" + elif task.task_type == "Content Creation": + args["content_type"] = "new content" + # Execute the skill via the tool registry if hasattr(self.agent, "_tools") and self.agent._tools: result = self.agent._tools.execute(tool_name, args) diff --git a/cheddahbot/tools/content_creation.py b/cheddahbot/tools/content_creation.py index 96ddc66..fdf6255 100644 --- a/cheddahbot/tools/content_creation.py +++ b/cheddahbot/tools/content_creation.py @@ -741,10 +741,10 @@ def _sync_clickup_optimization_complete( @tool( "create_content", - "Two-phase SEO content creation: Phase 1 researches + outlines, Phase 2 writes " - "full content from the approved outline. Auto-detects phase from ClickUp task " - "status ('outline approved' → Phase 2). " - "Auto-detects content type from URL presence if not specified.", + "SEO content creation. Set content_type='new content' for new pages " + "(Phase 1 outline → Phase 2 full write), or content_type='on page optimization' " + "to optimize an existing page with Cora data. Auto-detects phase from ClickUp " + "task status ('outline approved' → Phase 2). Ask the user which type if unclear.", category="content", ) def create_content( @@ -758,8 +758,9 @@ def create_content( Args: keyword: Primary target keyword (e.g. "plumbing services"). - url: Target page URL. If provided → on-page optimization; if empty → new content. - content_type: Type of content. Auto-detected from URL if empty. + url: Target page URL (optional for new content, required for optimization). + content_type: 'new content' or 'on page optimization'. Controls routing. + If empty, inferred from URL presence as fallback. cli_flags: Optional flags (e.g. "service" for service page hint). """ if not keyword: @@ -805,9 +806,10 @@ def create_content( capabilities_default = config.content.company_capabilities_default if config else "" - # Optimization path: URL present → run Phase 3 test block pipeline - # (skips the outline gate entirely) - if url: + # Optimization path: content_type determines route (URL fallback for chat callers) + if content_type.lower() == "on page optimization": + if not url: + return "Error: On Page Optimization requires a URL (IMSURL field)." return _run_optimization( agent=agent, config=config, diff --git a/tests/test_content_creation.py b/tests/test_content_creation.py index a073aea..e396d05 100644 --- a/tests/test_content_creation.py +++ b/tests/test_content_creation.py @@ -884,8 +884,8 @@ class TestSyncClickupOptimizationComplete: class TestCreateContentRouting: @patch("cheddahbot.tools.content_creation._run_optimization") - def test_url_present_routes_to_optimization(self, mock_opt, tmp_db, tmp_path): - """When URL is present, create_content should call _run_optimization.""" + def test_explicit_optimization_routes_correctly(self, mock_opt, tmp_db, tmp_path): + """When content_type='on page optimization', routes to _run_optimization.""" mock_opt.return_value = "## Optimization Complete" cfg = Config() cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines")) @@ -898,14 +898,15 @@ class TestCreateContentRouting: result = create_content( keyword="plumbing services", url="https://example.com/plumbing", + content_type="on page optimization", ctx=ctx, ) mock_opt.assert_called_once() assert result == "## Optimization Complete" @patch("cheddahbot.tools.content_creation._run_optimization") - def test_no_url_does_not_route_to_optimization(self, mock_opt, tmp_db, tmp_path): - """When URL is empty, create_content should NOT call _run_optimization.""" + def test_explicit_new_content_with_url_routes_to_phase1(self, mock_opt, tmp_db, tmp_path): + """Content Creation with URL should go to Phase 1, NOT optimization.""" cfg = Config() cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines")) agent = MagicMock() @@ -918,15 +919,58 @@ class TestCreateContentRouting: } result = create_content( keyword="new keyword", - url="", + url="https://example.com/future-page", + content_type="new content", ctx=ctx, ) mock_opt.assert_not_called() assert "Phase 1 Complete" in result + @patch("cheddahbot.tools.content_creation._run_optimization") + def test_optimization_without_url_returns_error(self, mock_opt, tmp_db, tmp_path): + """On Page Optimization without URL should return an error.""" + cfg = Config() + cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines")) + ctx = { + "agent": MagicMock(), + "config": cfg, + "db": tmp_db, + "clickup_task_id": "", + } + result = create_content( + keyword="plumbing services", + url="", + content_type="on page optimization", + ctx=ctx, + ) + mock_opt.assert_not_called() + assert "Error" in result + assert "URL" in result + + @patch("cheddahbot.tools.content_creation._run_optimization") + def test_fallback_url_routes_to_optimization(self, mock_opt, tmp_db, tmp_path): + """When content_type is empty and URL present, falls back to optimization.""" + mock_opt.return_value = "## Optimization Complete" + cfg = Config() + cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines")) + ctx = { + "agent": MagicMock(), + "config": cfg, + "db": tmp_db, + "clickup_task_id": "routing_test", + } + result = create_content( + keyword="plumbing services", + url="https://example.com/plumbing", + content_type="", + ctx=ctx, + ) + mock_opt.assert_called_once() + assert result == "## Optimization Complete" + @patch("cheddahbot.tools.content_creation._run_optimization") def test_new_content_still_calls_phase1(self, mock_opt, tmp_db, tmp_path): - """Regression: new content (no URL) still goes through _run_phase1.""" + """Regression: new content (no URL, no content_type) still goes through _run_phase1.""" cfg = Config() cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines")) agent = MagicMock()