Skip to content
Open
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
14 changes: 14 additions & 0 deletions docs/reference/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,20 @@ specify integration upgrade [<key>]

Reinstalls an installed integration with updated templates and commands (e.g., after upgrading Spec Kit). Defaults to the default integration; if a key is provided, it must be one of the installed integrations. Detects locally modified files and blocks the upgrade unless `--force` is used. Stale files from the previous install that are no longer needed are removed automatically. Shared templates stay aligned with the default integration even when upgrading a non-default integration.

## Diagnose Integration State

```bash
specify integration doctor
specify integration doctor --json
```

Inspects the current project's integration state without changing files. The
diagnostic report includes the default integration, installed integrations,
multi-install safety, missing managed files, modified managed files, invalid
manifest paths, shared Spec Kit infrastructure health, unchecked manifests, and
the target integration for default-sensitive shared templates. The JSON form is
intended for CI and coding agents that need stable machine-readable diagnostics.

## Integration-Specific Options

Some integrations accept additional options via `--integration-options`:
Expand Down
65 changes: 65 additions & 0 deletions src/specify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,71 @@ def integration_list(
console.print("Install one with: [cyan]specify integration install <key>[/cyan]")


def _print_integration_doctor_report(report: dict[str, Any]) -> None:
status = report["status"]
status_label = {
"ok": "[green]OK[/green]",
"warning": "[yellow]WARNING[/yellow]",
"error": "[red]ERROR[/red]",
}.get(status, status.upper())
installed = report.get("installed_integrations") or []

console.print(f"Integration state: {status_label}")
console.print(f"Default integration: {report.get('default_integration') or 'none'}")
console.print(f"Installed integrations: {', '.join(installed) if installed else 'none'}")
console.print(f"Multi-install safe: {'yes' if report.get('multi_install_safe') else 'no'}")
console.print(
f"Shared templates target alignment: "
f"{report.get('shared_templates_target_alignment') or 'none'}"
)
console.print(f"Modified managed files: {report.get('modified_managed_files', 0)}")
console.print(f"Missing managed files: {report.get('missing_managed_files', 0)}")
console.print(f"Invalid manifest paths: {report.get('invalid_manifest_paths', 0)}")
console.print(f"Unchecked manifests: {report.get('unchecked_manifests', 0)}")

findings = report.get("findings") or []
if not findings:
return

console.print()
console.print("[bold]Findings:[/bold]")
for item in findings:
severity = item.get("severity", "")
severity_label = {
"error": "[red]error[/red]",
"warning": "[yellow]warning[/yellow]",
}.get(severity, severity)
prefix = f"- {severity_label} {item.get('code', '')}"
if item.get("integration"):
prefix += f" ({item['integration']})"
console.print(f"{prefix}: {item.get('message', '')}", soft_wrap=True)
if item.get("suggestion"):
console.print(f" Suggestion: {item['suggestion']}", soft_wrap=True)


@integration_app.command("doctor")
def integration_doctor(
json_output: bool = typer.Option(
False,
"--json",
help="Emit machine-readable integration diagnostics.",
),
):
"""Diagnose the current project's integration state without changing files."""
from .integration_doctor import diagnose_integration_project

project_root = _require_specify_project()
report = diagnose_integration_project(project_root)

if json_output:
typer.echo(json.dumps(report, indent=2))
else:
_print_integration_doctor_report(report)

if report["status"] == "error":
raise typer.Exit(1)


@integration_app.command("install")
def integration_install(
key: str = typer.Argument(help="Integration key to install (e.g. claude, copilot)"),
Expand Down
Loading