diff --git a/src/millstone/runtime/orchestrator.py b/src/millstone/runtime/orchestrator.py index 50c6c85..f9d33b3 100755 --- a/src/millstone/runtime/orchestrator.py +++ b/src/millstone/runtime/orchestrator.py @@ -1976,6 +1976,24 @@ def estimate_remaining_time(self, pending_tasks: list[dict]) -> dict: ) def analyze_tasklist(self) -> dict: + from millstone.artifact_providers.mcp import MCPTasklistProvider + + provider = self._outer_loop_manager.tasklist_provider + if isinstance(provider, MCPTasklistProvider): + label_str = ", ".join(provider._labels) if provider._labels else "none" + print(f"Remote tasklist provider: {provider._mcp_server}") + print(f" Labels: {label_str}") + print() + print("Task analysis is not available for remote providers.") + print("Use --dry-run to inspect prompts that would be sent.") + return { + "pending_count": 0, + "completed_count": 0, + "total_count": 0, + "tasks": [], + "dependencies": [], + "suggested_order": [], + } # Delegates to TasklistManager return self._tasklist_manager.analyze_tasklist( estimate_time_callback=self.estimate_remaining_time, @@ -3271,22 +3289,32 @@ def run_dry_run(self) -> int: # Show tasklist file info if applicable if not self.task: print("--- Tasklist Info ---") - tasklist_path = self.repo_dir / self.tasklist - print(f" File: {tasklist_path}") - print(f" Exists: {tasklist_path.exists()}") - if tasklist_path.exists(): - content = tasklist_path.read_text() - unchecked = len(re.findall(r"^- \[ \]", content, re.MULTILINE)) - self.completed_task_count = self.count_completed_tasks() - print(f" Unchecked tasks: {unchecked}") - print(f" Completed tasks: {self.completed_task_count}") - print(f" Compact threshold: {self.compact_threshold}") - if self.should_compact(): - print(" Compaction: WOULD TRIGGER (completed >= threshold)") - elif self.compact_threshold <= 0: - print(" Compaction: DISABLED") - else: - print(" Compaction: not needed (completed < threshold)") + from millstone.artifact_providers.mcp import MCPTasklistProvider + + provider = self._outer_loop_manager.tasklist_provider + if isinstance(provider, MCPTasklistProvider): + print(f" Provider: {provider._mcp_server} (remote)") + label_str = ", ".join(provider._labels) if provider._labels else "none" + print(f" Labels: {label_str}") + project_str = ", ".join(provider._projects) if provider._projects else "none" + print(f" Projects: {project_str}") + else: + tasklist_path = self.repo_dir / self.tasklist + print(f" File: {tasklist_path}") + print(f" Exists: {tasklist_path.exists()}") + if tasklist_path.exists(): + content = tasklist_path.read_text() + unchecked = len(re.findall(r"^- \[ \]", content, re.MULTILINE)) + self.completed_task_count = self.count_completed_tasks() + print(f" Unchecked tasks: {unchecked}") + print(f" Completed tasks: {self.completed_task_count}") + print(f" Compact threshold: {self.compact_threshold}") + if self.should_compact(): + print(" Compaction: WOULD TRIGGER (completed >= threshold)") + elif self.compact_threshold <= 0: + print(" Compaction: DISABLED") + else: + print(" Compaction: not needed (completed < threshold)") print() # Show work directory info diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py index 2deeecc..599ae10 100644 --- a/tests/test_orchestrator.py +++ b/tests/test_orchestrator.py @@ -3194,6 +3194,25 @@ def test_dry_run_shows_compaction_disabled(self, temp_repo, capsys): finally: orch.cleanup() + def test_dry_run_shows_mcp_provider_info(self, temp_repo, capsys): + """Dry run with MCP provider shows provider name and labels, not file path.""" + from millstone.artifact_providers.mcp import MCPTasklistProvider + + orch = Orchestrator(dry_run=True) + try: + provider = MCPTasklistProvider("github", labels=["millstone"]) + orch._outer_loop_manager.tasklist_provider = provider + orch.run() + captured = capsys.readouterr() + assert "Provider: github (remote)" in captured.out + assert "Labels: millstone" in captured.out + # Tasklist Info section should not show file-based info + tasklist_section = captured.out.split("--- Tasklist Info ---")[1].split("---")[0] + assert "File:" not in tasklist_section + assert "Unchecked tasks:" not in tasklist_section + finally: + orch.cleanup() + def test_run_compaction_logs_event(self, temp_repo): """run_compaction logs compaction events.""" tasklist_path = temp_repo / ".millstone" / "tasklist.md" @@ -15568,6 +15587,25 @@ def test_analyze_tasklist_shows_no_file_message(self, temp_repo, capsys): finally: orch.cleanup() + def test_analyze_tasklist_with_mcp_provider(self, temp_repo, capsys): + """--analyze-tasklist with MCP provider shows remote info, not file error.""" + from millstone.artifact_providers.mcp import MCPTasklistProvider + + orch = Orchestrator(dry_run=False, quiet=True) + try: + provider = MCPTasklistProvider("github", labels=["millstone"]) + orch._outer_loop_manager.tasklist_provider = provider + result = orch.analyze_tasklist() + + captured = capsys.readouterr() + assert "Remote tasklist provider: github" in captured.out + assert "Labels: millstone" in captured.out + assert "Tasklist not found" not in captured.out + assert result["pending_count"] == 0 + assert result["total_count"] == 0 + finally: + orch.cleanup() + def test_analyze_tasklist_counts_tasks(self, temp_repo, capsys): """--analyze-tasklist reports correct task counts.""" tasklist = temp_repo / "docs" / "tasklist.md"