Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0e55a1e
feat(integration): add doctor diagnostics
PascalThuet May 22, 2026
9a70826
fix(integration): address doctor review feedback
PascalThuet May 22, 2026
5d5587a
fix(integration): harden doctor diagnostics
PascalThuet May 22, 2026
d420ce6
fix(integration): rename doctor diagnostics to status
PascalThuet May 26, 2026
81ed372
fix(integration): address status review feedback
PascalThuet May 26, 2026
485b143
fix(integration): validate status manifest keys
PascalThuet May 26, 2026
b3a245b
fix(integration): escape status report output
PascalThuet May 26, 2026
7d505aa
fix(integration): address status review feedback
PascalThuet May 27, 2026
40477a2
fix(integration): harden status manifest checks
PascalThuet May 27, 2026
ee8ddc0
fix(integration): tighten status diagnostics
PascalThuet May 27, 2026
99db1bb
fix(integration): clarify status state sources
PascalThuet May 27, 2026
9dedcf4
fix(integration): report unknown multi-install safety
PascalThuet May 28, 2026
1596fe2
fix(integration): mark empty safety as unknown
PascalThuet May 28, 2026
934b152
fix(integration): ignore invalid raw installed list
PascalThuet May 28, 2026
c8d9faa
fix(integration): report actual manifest checks
PascalThuet May 28, 2026
646ab9d
fix(integration): tighten status contract invariants
PascalThuet May 28, 2026
982e8a6
fix(integration): harden manifest status paths
PascalThuet May 28, 2026
4b40ef2
fix(integration): avoid symlink target stat
PascalThuet May 28, 2026
6d5383e
fix(integration): clarify status edge cases
PascalThuet May 28, 2026
67d28d4
test(integration): pin status filesystem guards
PascalThuet May 28, 2026
928d89d
fix(integration): tighten status path checks
PascalThuet May 28, 2026
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, shared
Spec Kit infrastructure health, unchecked manifests, and any manifest or
state-file problems. 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
61 changes: 61 additions & 0 deletions src/specify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,67 @@ 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 aligned to: {report.get('shared_templates_aligned_to') 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"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)
Comment thread
PascalThuet marked this conversation as resolved.
Outdated


@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