Skip to content

symfonic.diagnostics.cli

cli

Typer command implementation for symfonic doctor (v7.16, item 5).

Separated from :mod:symfonic.agent.cli.main so the CLI module can stay small and so the command surface can be unit-tested in isolation via a CliRunner against a focused sub-app.

Behaviour:

  • Optional PROJECT_PATH positional argument -- when present, the command prepends it to sys.path and tries to import the scaffold convention from app.setup.agent import build_agent. When absent, a default :class:SymfonicAgent is constructed with a :class:MockModelProvider so the env-var checks (embedding, postgres) and the agent-config checks still run.
  • --strict promotes WARN findings to exit code 2 (mypy-style).
  • --no-network forces network-sensitive checks to skip; the default honours CI=true.
  • --json emits a single JSON document on stdout and suppresses the human-readable progress lines on stderr.

doctor

doctor(project_path: Annotated[Path | None, Argument(help='Adopter project root containing ``app/setup/agent.py``. When omitted, a default in-memory agent is constructed -- env-var checks still run.')] = None, strict: Annotated[bool, Option('--strict', help='Promote WARN findings to exit code 2.')] = False, no_network: Annotated[bool | None, Option('--no-network/--network', help='Skip network-sensitive checks (POSTGRES_DSN probe). Default honours CI=true in the environment.')] = None, json_output: Annotated[bool, Option('--json', help='Emit a single JSON document on stdout instead of the human-readable report.  Suppresses all stderr progress.')] = False, offline: Annotated[bool, Option('--offline', help='Run the migration inspection instead of the live audit: no agent is constructed, nothing in PROJECT_PATH is imported, and no socket is opened.')] = False, config: Annotated[Path | None, Option('--config', help='JSON or TOML file holding the raw adopter configuration to inspect (--offline only).')] = None, env_file: Annotated[Path | None, Option('--env-file', help='KEY=value file holding the environment the inspected deployment runs with. Without it the platform-security checks are recorded as not inspected rather than answered from this machine (--offline only).')] = None, migration_report: Annotated[Path | None, Option('--migration-report', help='Write the Markdown migration inspection report to this path (--offline only).')] = None, package_version: Annotated[str | None, Option('--package-version', help='symfonic-core version of the inspected deployment (--offline only).')] = None) -> None

Audit the current symfonic configuration for the v7.16 silent-fail traps.

Source code in symfonic/diagnostics/cli.py
def doctor(
    project_path: Annotated[
        Path | None,
        typer.Argument(
            help="Adopter project root containing ``app/setup/agent.py``. "
            "When omitted, a default in-memory agent is constructed -- "
            "env-var checks still run.",
        ),
    ] = None,
    strict: Annotated[
        bool,
        typer.Option(
            "--strict",
            help="Promote WARN findings to exit code 2.",
        ),
    ] = False,
    no_network: Annotated[
        bool | None,
        typer.Option(
            "--no-network/--network",
            help=(
                "Skip network-sensitive checks (POSTGRES_DSN probe). "
                "Default honours CI=true in the environment."
            ),
        ),
    ] = None,
    json_output: Annotated[
        bool,
        typer.Option(
            "--json",
            help=(
                "Emit a single JSON document on stdout instead of the "
                "human-readable report.  Suppresses all stderr progress."
            ),
        ),
    ] = False,
    offline: Annotated[
        bool,
        typer.Option(
            "--offline",
            help=(
                "Run the migration inspection instead of the live audit: no "
                "agent is constructed, nothing in PROJECT_PATH is imported, and "
                "no socket is opened."
            ),
        ),
    ] = False,
    config: Annotated[
        Path | None,
        typer.Option(
            "--config",
            help=(
                "JSON or TOML file holding the raw adopter configuration to "
                "inspect (--offline only)."
            ),
        ),
    ] = None,
    env_file: Annotated[
        Path | None,
        typer.Option(
            "--env-file",
            help=(
                "KEY=value file holding the environment the inspected deployment "
                "runs with. Without it the platform-security checks are recorded "
                "as not inspected rather than answered from this machine "
                "(--offline only)."
            ),
        ),
    ] = None,
    migration_report: Annotated[
        Path | None,
        typer.Option(
            "--migration-report",
            help=(
                "Write the Markdown migration inspection report to this path "
                "(--offline only)."
            ),
        ),
    ] = None,
    package_version: Annotated[
        str | None,
        typer.Option(
            "--package-version",
            help=(
                "symfonic-core version of the inspected deployment "
                "(--offline only)."
            ),
        ),
    ] = None,
) -> None:
    """Audit the current symfonic configuration for the v7.16 silent-fail traps."""
    _reject_offline_only_options(
        offline,
        config=config,
        env_file=env_file,
        migration_report=migration_report,
        package_version=package_version,
    )
    if offline:
        _run_offline_mode(
            project_path,
            config=config,
            env_file=env_file,
            migration_report=migration_report,
            package_version=package_version,
            strict=strict,
            json_output=json_output,
        )
        return

    agent = _load_agent(project_path, suppress_stderr=json_output)
    effective_no_network = resolve_no_network(explicit_flag=no_network)
    report = audit_hms_sync(agent, no_network=effective_no_network)

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

    raise SystemExit(report.exit_code(strict=strict))