Coverage for src / moai_adk / cli / commands / doctor.py: 16.67%
132 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
1"""MoAI-ADK doctor command
3System diagnostics command:
4- Check the Python version
5- Verify Git installation
6- Validate project structure
7- Inspect language-specific tool chains
8- Diagnose slash command loading issues (--check-commands)
10## Skill Invocation Guide (English-Only)
12### Related Skills
13- **moai-foundation-langs**: For language toolchain verification and detection
14 - Trigger: Use `--verbose` or `--fix` flag to inspect language-specific tools
15 - Invocation: `Skill("moai-foundation-langs")` for detailed language stack analysis
17- **moai-foundation-trust**: For TRUST 5-principles verification after fixing tools
18 - Trigger: After running doctor with `--fix` to validate improvements
19 - Invocation: `Skill("moai-foundation-trust")` to verify code quality toolchain
21### When to Invoke Skills in Related Workflows
221. **After doctor diagnosis**:
23 - Run `Skill("moai-foundation-trust")` to validate that all TRUST tools are properly configured
24 - Run `Skill("moai-foundation-langs")` to confirm language-specific toolchains
262. **When tools are missing** (`--fix` flag):
27 - Use suggested fixes from doctor command
28 - Follow up with `Skill("moai-foundation-langs")` to validate corrections
303. **Debugging slash command issues** (`--check-commands`):
31 - Run `Skill("moai-cc-commands")` if commands fail to load
32 - Check `.claude/commands/` directory structure and permissions
33"""
35import json
36from pathlib import Path
38import click
39import questionary
40from rich.console import Console
41from rich.table import Table
43from moai_adk.core.project.checker import SystemChecker, check_environment
44from moai_adk.core.project.detector import detect_project_language
46console = Console()
49@click.command()
50@click.option(
51 "--verbose",
52 "-v",
53 is_flag=True,
54 help="Show detailed tool versions and language detection",
55)
56@click.option("--fix", is_flag=True, help="Suggest fixes for missing tools")
57@click.option("--export", type=click.Path(), help="Export diagnostics to JSON file")
58@click.option("--check", type=str, help="Check specific tool only")
59@click.option(
60 "--check-commands", is_flag=True, help="Diagnose slash command loading issues"
61)
62def doctor(
63 verbose: bool,
64 fix: bool,
65 export: str | None,
66 check: str | None,
67 check_commands: bool,
68) -> None:
69 """Check system requirements and project health
71 Verifies:
72 - Python version (>= 3.13)
73 - Git installation
74 - Project structure (.moai directory)
75 - Language-specific tool chains (20+ languages)
76 """
77 try:
78 # Handle --check-commands option first
79 if check_commands:
80 _check_slash_commands()
81 return
83 console.print("[cyan]Running system diagnostics...[/cyan]\n")
85 # Run basic environment checks
86 results = check_environment()
87 diagnostics_data: dict = {"basic_checks": results}
89 # In verbose mode, verify language-specific toolchains
90 if verbose or fix:
91 language = detect_project_language()
92 diagnostics_data["detected_language"] = language
94 if verbose:
95 console.print(
96 f"[dim]Detected language: {language or 'Unknown'}[/dim]\n"
97 )
99 if language:
100 checker = SystemChecker()
101 language_tools = checker.check_language_tools(language)
102 diagnostics_data["language_tools"] = language_tools
104 if verbose:
105 _display_language_tools(language, language_tools, checker)
107 # Specific tool check
108 if check:
109 _check_specific_tool(check)
110 return
112 # Build the base results table
113 table = Table(show_header=True, header_style="bold magenta")
114 table.add_column("Check", style="dim", width=40)
115 table.add_column("Status", justify="center")
117 for check_name, status in results.items():
118 icon = "✓" if status else "✗"
119 color = "green" if status else "red"
120 table.add_row(check_name, f"[{color}]{icon}[/{color}]")
122 console.print(table)
124 # In fix mode, suggest installation commands for missing tools
125 if fix and "language_tools" in diagnostics_data:
126 _suggest_fixes(
127 diagnostics_data["language_tools"],
128 diagnostics_data.get("detected_language"),
129 )
131 # When exporting, write diagnostics to JSON
132 if export:
133 _export_diagnostics(export, diagnostics_data)
135 # Summarize the overall result
136 all_passed = all(results.values())
137 if all_passed:
138 console.print("\n[green]✓ All checks passed[/green]")
139 else:
140 console.print("\n[yellow]⚠ Some checks failed[/yellow]")
141 console.print(
142 "[dim]Run [cyan]python -m moai_adk doctor --verbose[/cyan] for detailed diagnostics[/dim]"
143 )
145 except Exception as e:
146 console.print(f"[red]✗ Diagnostic failed: {e}[/red]")
147 raise
150def _display_language_tools(
151 language: str, tools: dict[str, bool], checker: SystemChecker
152) -> None:
153 """Display a table of language-specific tools (helper)"""
154 table = Table(
155 show_header=True, header_style="bold cyan", title=f"{language.title()} Tools"
156 )
157 table.add_column("Tool", style="dim")
158 table.add_column("Status", justify="center")
159 table.add_column("Version", style="blue")
161 for tool, available in tools.items():
162 icon = "✓" if available else "✗"
163 color = "green" if available else "red"
164 version = checker.get_tool_version(tool) if available else "not installed"
166 table.add_row(tool, f"[{color}]{icon}[/{color}]", version or "")
168 console.print(table)
169 console.print()
172def _check_specific_tool(tool: str) -> None:
173 """Check only a specific tool (helper)"""
174 checker = SystemChecker()
175 available = checker._is_tool_available(tool)
176 version = checker.get_tool_version(tool) if available else None
178 if available:
179 console.print(f"[green]✓ {tool} is installed[/green]")
180 if version:
181 console.print(f" Version: {version}")
182 else:
183 console.print(f"[red]✗ {tool} is not installed[/red]")
186def _suggest_fixes(tools: dict[str, bool], language: str | None) -> None:
187 """Suggest installation commands for missing tools (helper)"""
188 missing_tools = [tool for tool, available in tools.items() if not available]
190 if not missing_tools:
191 console.print("\n[green]✓ All tools are installed[/green]")
192 return
194 console.print(f"\n[yellow]⚠ Missing {len(missing_tools)} tool(s)[/yellow]")
196 try:
197 proceed = questionary.confirm(
198 "Would you like to see install suggestions for missing tools?",
199 default=True,
200 ).ask()
201 except Exception:
202 proceed = True
204 if not proceed:
205 console.print("[yellow]User skipped install suggestions[/yellow]")
206 return
208 for tool in missing_tools:
209 install_cmd = _get_install_command(tool, language)
210 console.print(f" [red]✗[/red] {tool}")
211 if install_cmd:
212 console.print(f" Install: [cyan]{install_cmd}[/cyan]")
215def _get_install_command(tool: str, language: str | None) -> str:
216 """Return the install command for a given tool (helper)"""
217 # Common tools with preferred package managers
218 install_commands = {
219 # Python tools (prefer uv)
220 "pytest": "uv pip install pytest",
221 "mypy": "uv pip install mypy",
222 "ruff": "uv pip install ruff",
223 # JavaScript tools
224 "vitest": "npm install -D vitest",
225 "biome": "npm install -D @biomejs/biome",
226 "eslint": "npm install -D eslint",
227 "jest": "npm install -D jest",
228 }
230 return install_commands.get(tool, f"# Install {tool} for {language}")
233def _export_diagnostics(export_path: str, data: dict) -> None:
234 """Export diagnostic results to a JSON file (helper)"""
235 try:
236 output = Path(export_path)
237 output.write_text(json.dumps(data, indent=2))
238 console.print(f"\n[green]✓ Diagnostics exported to {export_path}[/green]")
239 except Exception as e:
240 console.print(f"\n[red]✗ Failed to export diagnostics: {e}[/red]")
243def _check_slash_commands() -> None:
244 """Check slash command loading issues (helper)"""
245 from moai_adk.core.diagnostics.slash_commands import diagnose_slash_commands
247 console.print("[cyan]Running slash command diagnostics...[/cyan]\n")
249 result = diagnose_slash_commands()
251 # Handle error case
252 if "error" in result:
253 console.print(f"[red]✗ {result['error']}[/red]")
254 return
256 # Build results table
257 table = Table(show_header=True, header_style="bold magenta")
258 table.add_column("Command File", style="dim", width=40)
259 table.add_column("Status", justify="center", width=10)
260 table.add_column("Issues", style="yellow")
262 for detail in result["details"]:
263 icon = "✓" if detail["valid"] else "✗"
264 color = "green" if detail["valid"] else "red"
265 issues = ", ".join(detail["errors"]) if detail["errors"] else "-"
267 table.add_row(detail["file"], f"[{color}]{icon}[/{color}]", issues)
269 console.print(table)
270 console.print()
272 # Summary
273 total = result["total_files"]
274 valid = result["valid_commands"]
276 if valid == total and total > 0:
277 console.print(f"[green]✓ {valid}/{total} command files are valid[/green]")
278 elif total == 0:
279 console.print("[yellow]⚠ No command files found in .claude/commands/[/yellow]")
280 else:
281 console.print(
282 f"[yellow]⚠ Only {valid}/{total} command files are valid[/yellow]"
283 )
284 console.print("[dim]Fix the issues above to enable slash commands[/dim]")