Skip to main content
Glama
plan.py5.8 kB
"""Plan phase handler.""" import re from typing import Any from fastmcp.server.context import Context from pathfinder_mcp.handlers.base import BaseHandler from pathfinder_mcp.state import Phase class PlanHandler(BaseHandler): """Handler for plan phase operations.""" phase = Phase.PLAN async def execute( self, session_id: str, plan_content: str | None = None, ctx: Context | None = None, **kwargs: Any, ) -> dict[str, Any]: """Execute plan operation. If plan_content is None, starts plan phase (transitions from research). If plan_content is provided, saves the plan. Args: session_id: Session ID plan_content: Plan content to save (None to start plan phase) ctx: FastMCP context for elicitation Returns: Operation result """ if plan_content is not None: return self._save_plan(session_id, plan_content) return await self._start_plan(session_id, ctx) async def _start_plan(self, session_id: str, ctx: Context | None) -> dict[str, Any]: """Transition to plan phase.""" # Validate in research phase error = self.validate_phase(session_id, [Phase.RESEARCH]) if error: return error # Check research exists if not self.artifact_writer.artifact_exists(session_id, "research.md"): return { "error": "Research artifact not found. Complete research first.", "code": "MISSING_RESEARCH", } # Elicit confirmation if ctx is not None: try: result = await ctx.elicit( "Ready to transition to planning phase?", response_type=["yes", "no"], ) if result.action != "accept" or result.data == "no": return { "session_id": session_id, "phase": "research", "message": "Plan transition cancelled.", "cancelled": True, } except Exception: pass # Elicit not supported # Get task name from research research = self.artifact_writer.read_artifact(session_id, "research.md") or "" match = re.search(r"^# Research: (.+)$", research, re.MULTILINE) task_name = match.group(1) if match else "Implementation Plan" # Transition state = self.get_session(session_id) if state: new_state = state.transition_to(Phase.PLAN) self.set_session(session_id, new_state) # Create template template = self.artifact_writer.get_plan_template( name=task_name, overview=f"Implementation plan for: {task_name}" ) artifact_path = self.artifact_writer.write_plan(session_id, template) # Update snapshot snapshot = self.session_manager.load_snapshot(session_id) if snapshot: snapshot.phase = Phase.PLAN self.session_manager.save_snapshot(session_id, snapshot) return { "session_id": session_id, "phase": "plan", "artifact_path": str(artifact_path), "message": "Plan phase started. Edit plan.md.", "template_created": True, } def _save_plan(self, session_id: str, plan_content: str) -> dict[str, Any]: """Save plan content.""" # Validate in plan phase error = self.validate_phase(session_id, [Phase.PLAN]) if error: return error # Validate structure is_valid, errors = self._validate_structure(plan_content) if not is_valid: return { "error": "Plan validation failed", "code": "INVALID_PLAN_FORMAT", "validation_errors": errors, } # Save artifact_path = self.artifact_writer.write_plan(session_id, plan_content) self.context_monitor.add_message(plan_content) # Update snapshot snapshot = self.session_manager.load_snapshot(session_id) if snapshot: snapshot.plan_summary = plan_content[:500] snapshot.context_tokens = self.context_monitor.current_tokens self.session_manager.save_snapshot(session_id, snapshot) return { "session_id": session_id, "artifact_path": str(artifact_path), "validated": True, "context": self.get_context_status(), "message": "Plan saved. Use implement_phase to begin.", } def _validate_structure(self, content: str) -> tuple[bool, list[str]]: """Validate plan structure.""" errors: list[str] = [] if not content.startswith("---"): errors.append("Missing YAML frontmatter") else: parts = content.split("---", 2) if len(parts) < 3: errors.append("Invalid YAML frontmatter") else: fm = parts[1] for field in ["name:", "overview:", "todos:"]: if field not in fm: errors.append(f"Missing {field.rstrip(':')}") required = [ ("## Architecture Overview", "Architecture Overview"), ("## Core Components", "Core Components"), ("## Phase 1", "Phase section"), ("## Implementation Details", "Implementation Details"), ("## Success Criteria", "Success Criteria"), ] for pattern, name in required: if pattern not in content: errors.append(f"Missing {name}") return len(errors) == 0, errors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jamesctucker/pathfinder-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server