diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index dac7eaa5..c5a4b6c0 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1775,6 +1775,142 @@ def version(): console.print() +@app.command() +def status(): + """Show current project status and SDD workflow progress.""" + show_banner() + + project_root = Path.cwd() + + # Check if we're in a spec-kit project + specify_dir = project_root / ".specify" + if not specify_dir.exists(): + console.print("[red]Error:[/red] Not a spec-kit project (no .specify/ directory)") + console.print("Run [cyan]specify init[/cyan] first to create a project, or cd into an existing one.") + raise typer.Exit(1) + + project_name = project_root.name + + # --- Detect AI agent(s) by scanning for known agent folders --- + detected_agents = [] + for agent_key, agent_config in AGENT_CONFIG.items(): + if agent_key == "generic": + continue + folder = agent_config.get("folder") + if folder and (project_root / folder.rstrip("/")).is_dir(): + commands_subdir = agent_config.get("commands_subdir", "commands") + commands_dir = project_root / folder.rstrip("/") / commands_subdir + has_commands = commands_dir.is_dir() and any(commands_dir.iterdir()) + detected_agents.append({ + "key": agent_key, + "name": agent_config["name"], + "folder": folder, + "has_commands": has_commands, + }) + + # --- Detect script type --- + script_type = None + if (specify_dir / "scripts" / "bash").is_dir(): + script_type = "sh" + if (specify_dir / "scripts" / "powershell").is_dir(): + script_type = "ps" if script_type is None else "sh + ps" + + # --- Detect current feature --- + current_branch = None + has_git = False + + # 1. Check SPECIFY_FEATURE env var + env_feature = os.environ.get("SPECIFY_FEATURE", "").strip() + if env_feature: + current_branch = env_feature + + # 2. Try git branch + if not current_branch: + try: + result = subprocess.run( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], + capture_output=True, text=True, timeout=5, + cwd=str(project_root), + ) + if result.returncode == 0: + has_git = True + current_branch = result.stdout.strip() + except (FileNotFoundError, subprocess.TimeoutExpired): + pass + + # 3. Fallback: scan specs/ for highest-numbered directory + if not current_branch or not current_branch[0:3].isdigit(): + specs_dir = project_root / "specs" + if specs_dir.is_dir(): + highest_num = 0 + latest_feature = None + for d in specs_dir.iterdir(): + if d.is_dir() and len(d.name) >= 4 and d.name[:3].isdigit() and d.name[3] == "-": + num = int(d.name[:3]) + if num > highest_num: + highest_num = num + latest_feature = d.name + if latest_feature and (not current_branch or current_branch in ("main", "master")): + current_branch = latest_feature + + # --- Resolve feature directory (prefix-based matching like common.sh) --- + feature_dir = None + if current_branch and len(current_branch) >= 4 and current_branch[:3].isdigit() and current_branch[3] == "-": + prefix = current_branch[:3] + specs_dir = project_root / "specs" + if specs_dir.is_dir(): + matches = [d for d in specs_dir.iterdir() if d.is_dir() and d.name.startswith(f"{prefix}-")] + if len(matches) == 1: + feature_dir = matches[0] + elif len(matches) > 1: + # Try exact match first + exact = specs_dir / current_branch + if exact.is_dir(): + feature_dir = exact + else: + feature_dir = matches[0] # Use first match + + # --- Build output --- + info_table = Table(show_header=False, box=None, padding=(0, 2)) + info_table.add_column("Key", style="cyan", justify="right", min_width=16) + info_table.add_column("Value", style="white") + + info_table.add_row("Project", f"[bold]{project_name}[/bold]") + + if detected_agents: + for i, agent in enumerate(detected_agents): + label = "AI Agent" if i == 0 else "" + cmd_status = "[green]commands present[/green]" if agent["has_commands"] else "[yellow]no commands[/yellow]" + info_table.add_row(label, f"{agent['name']} ({agent['folder']}) {cmd_status}") + else: + info_table.add_row("AI Agent", "[yellow]none detected[/yellow]") + + if script_type: + info_table.add_row("Script Type", script_type) + + if has_git: + info_table.add_row("Git Branch", current_branch or "[dim]unknown[/dim]") + else: + if env_feature: + info_table.add_row("Feature", f"{env_feature} [dim](from SPECIFY_FEATURE)[/dim]") + else: + info_table.add_row("Git", "[yellow]not available[/yellow]") + + if feature_dir: + info_table.add_row("Feature Dir", f"specs/{feature_dir.name}/") + elif current_branch and current_branch not in ("main", "master"): + info_table.add_row("Feature Dir", f"[yellow]not found for {current_branch}[/yellow]") + + panel = Panel( + info_table, + title="[bold cyan]Specify Project Status[/bold cyan]", + border_style="cyan", + padding=(1, 2), + ) + console.print(panel) + console.print() + + # ===== Extension Commands ===== extension_app = typer.Typer(