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
60 changes: 44 additions & 16 deletions src/millstone/runtime/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
38 changes: 38 additions & 0 deletions tests/test_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down