Manages a spec creation conversation for one project. Uses the create-spec skill to guide users through: - Phase 1: Project Overview (name, description, audience) - Phase 2: Involvement Level (Quick vs Detailed mode) - Phase 3: Technology Preferences - Phase 4: Features (ma
| 28 | |
| 29 | |
| 30 | class SpecChatSession: |
| 31 | """ |
| 32 | Manages a spec creation conversation for one project. |
| 33 | |
| 34 | Uses the create-spec skill to guide users through: |
| 35 | - Phase 1: Project Overview (name, description, audience) |
| 36 | - Phase 2: Involvement Level (Quick vs Detailed mode) |
| 37 | - Phase 3: Technology Preferences |
| 38 | - Phase 4: Features (main exploration phase) |
| 39 | - Phase 5: Technical Details (derived or discussed) |
| 40 | - Phase 6-7: Success Criteria & Approval |
| 41 | """ |
| 42 | |
| 43 | def __init__(self, project_name: str, project_dir: Path): |
| 44 | """ |
| 45 | Initialize the session. |
| 46 | |
| 47 | Args: |
| 48 | project_name: Name of the project being created |
| 49 | project_dir: Absolute path to the project directory |
| 50 | """ |
| 51 | self.project_name = project_name |
| 52 | self.project_dir = project_dir |
| 53 | self.client: Optional[ClaudeSDKClient] = None |
| 54 | self.messages: list[dict] = [] |
| 55 | self.complete: bool = False |
| 56 | self.created_at = datetime.now() |
| 57 | self._conversation_id: Optional[str] = None |
| 58 | self._client_entered: bool = False # Track if context manager is active |
| 59 | |
| 60 | async def close(self) -> None: |
| 61 | """Clean up resources and close the Claude client.""" |
| 62 | if self.client and self._client_entered: |
| 63 | try: |
| 64 | await self.client.__aexit__(None, None, None) |
| 65 | except Exception as e: |
| 66 | logger.warning(f"Error closing Claude client: {e}") |
| 67 | finally: |
| 68 | self._client_entered = False |
| 69 | self.client = None |
| 70 | |
| 71 | async def start(self) -> AsyncGenerator[dict, None]: |
| 72 | """ |
| 73 | Initialize session and get initial greeting from Claude. |
| 74 | |
| 75 | Yields message chunks as they stream in. |
| 76 | """ |
| 77 | # Load the create-spec skill |
| 78 | skill_path = ROOT_DIR / ".claude" / "commands" / "create-spec.md" |
| 79 | |
| 80 | if not skill_path.exists(): |
| 81 | yield { |
| 82 | "type": "error", |
| 83 | "content": f"Spec creation skill not found at {skill_path}" |
| 84 | } |
| 85 | return |
| 86 | |
| 87 | try: |