harn-stdlib 0.10.182

Embedded Harn standard library source catalog
Documentation
import { cli_locale } from "std/cli/localization"
import { cli_message } from "std/cli/localization/doctor"
import { safe_bool, safe_dict, safe_int, safe_list, safe_string } from "std/cli/render"

/**
 * `harn doctor` rendering layer ported to .harn — see harn#2312 (W12).
 *
 * **Pragmatic partial port.** The actual probes (toolchain, providers,
 * MCP, file permissions, manifest health, capability matrix, hardware
 * snapshot, ollama, target probes) stay in Rust because each one
 * reaches into a different VM/host facility (subprocess execution,
 * `harn_vm::llm`, `notify::recommended_watcher`, `runtime_paths`,
 * trigger registry snapshots, …) and is a multi-file refactor on its
 * own. The Rust shim runs every check, assembles the structured
 * `DoctorReport`, serialises it to JSON, and hands it across the
 * dispatch wedge for formatting.
 *
 * What this script owns: the human-readable report layout (sections,
 * pad widths, status uppercasing, suggested-fix indenting) and the
 * pass-through of the JSON envelope. That's the surface a user reads
 * or pipes into another tool, so it's the surface we want under .harn.
 *
 * Inputs (from the dispatch shim in
 * crates/harn-cli/src/commands/doctor.rs):
 *   HARN_DOCTOR_REPORT_JSON          — JSON-serialised `DoctorReport`
 *     (see the struct in doctor.rs for the canonical shape).
 *   HARN_DOCTOR_REPORT_ENVELOPE_JSON — JSON-serialised
 *     `JsonEnvelope<DoctorReport>` — used verbatim for `--json` so the
 *     byte stream preserves serde's declaration-order field layout
 *     and `null` for absent fields. Harn's `json_stringify_pretty`
 *     would alphabetise the keys, which would break downstream JSON
 *     consumers that expect the declared shape.
 *   HARN_OUTPUT_JSON                 — "1" for the JSON envelope, else
 *                                       human-readable text.
 */
/**
 * Uppercase an ASCII status string (`ok` → `OK`, `warn` → `WARN`,
 * etc). Harn's stdlib doesn't expose `to_uppercase` as a free
 * builtin, so do the four cases by hand — there are only ever four
 * status values.
 */
fn __status_upper(__cli_locale: string, status: string) -> string {
  if status == "ok" {
    return cli_message(__cli_locale, "OK", [])
  }
  if status == "warn" {
    return cli_message(__cli_locale, "WARN", [])
  }
  if status == "fail" {
    return cli_message(__cli_locale, "FAIL", [])
  }
  if status == "skip" {
    return cli_message(__cli_locale, "SKIP", [])
  }
  return status
}

/**
 * Render the `checks:` section. Layout matches the host formatter:
 *   `{status:>4}  {label:<24} {detail}`
 * where status is uppercased, padded to width 4 with a leading space,
 * and label is left-justified to width 24. Failing/warning checks
 * also emit indented `fix:`, `docs:`, and `blocks:` lines beneath
 * their primary row.
 */
fn __render_checks_section(__cli_locale: string, checks: list) -> string {
  let out = ""
  for raw_check in checks {
    const check = safe_dict(raw_check)
    const status = safe_string(check["status"], "")
    const label = safe_string(check["label"], "")
    const detail = safe_string(check["detail"], "")
    const status_upper = __status_upper(__cli_locale, status)
    const status_padded = str_pad(status_upper, 4, " ", "left")
    const label_padded = str_pad(label, 24, " ", "right")
    out = out + status_padded + "  " + label_padded + " " + detail + "\n"
    if status != "ok" && status != "skip" {
      const fix = check["fix_command"]
      if type_of(fix) == "string" && fix != "" {
        out = cli_message(__cli_locale, "{0}       fix: {1}\n", [to_string(out), to_string(fix)])
      }
      const docs = check["docs_url"]
      if type_of(docs) == "string" && docs != "" {
        out = cli_message(__cli_locale, "{0}       docs: {1}\n", [to_string(out), to_string(docs)])
      }
      const blocks = safe_list(check["blocks"])
      if len(blocks) > 0 {
        out = cli_message(
          __cli_locale,
          "{0}       blocks: {1}\n",
          [to_string(out), to_string(join(blocks, ", "))],
        )
      }
    }
  }
  return out
}

fn __render_targets_section(__cli_locale: string, targets: list) -> string {
  let out = cli_message(__cli_locale, "--- Targets ---\n", [])
  for raw_target in targets {
    const target = safe_dict(raw_target)
    const triple = safe_string(target["triple"], "")
    const triple_padded = str_pad(triple, 32, " ", "right")
    const installed = if safe_bool(target["installed"], false) {
      cli_message(__cli_locale, "installed", [])
    } else {
      cli_message(__cli_locale, "missing", [])
    }
    const buildable_raw = target["buildable"]
    const buildable = if buildable_raw == nil {
      cli_message(__cli_locale, "not probed", [])
    } else if safe_bool(buildable_raw, false) {
      cli_message(__cli_locale, "buildable", [])
    } else {
      cli_message(__cli_locale, "not buildable", [])
    }
    out = out + "  " + triple_padded + " " + installed + ", " + buildable + "\n"
    const reasons = safe_list(target["reasons"])
    for reason in reasons {
      if type_of(reason) == "string" {
        out = out + "       " + reason + "\n"
      }
    }
  }
  return out
}

fn __render_providers_section(__cli_locale: string, providers: list) -> string {
  let out = cli_message(__cli_locale, "--- Providers ---\n", [])
  for raw_provider in providers {
    const provider = safe_dict(raw_provider)
    const name = safe_string(provider["name"], "")
    const name_padded = str_pad(name, 24, " ", "right")
    const configured = if safe_bool(provider["configured"], false) {
      cli_message(__cli_locale, "configured", [])
    } else {
      cli_message(__cli_locale, "no credentials", [])
    }
    const probed = safe_bool(provider["probed"], false)
    const reachable_raw = provider["reachable"]
    const latency_raw = provider["latency_ms"]
    const reachable = if !probed {
      cli_message(__cli_locale, "not probed", [])
    } else if reachable_raw == nil {
      cli_message(__cli_locale, "probed", [])
    } else if safe_bool(reachable_raw, false) {
      if latency_raw == nil {
        cli_message(__cli_locale, "reachable", [])
      } else {
        cli_message(__cli_locale, "reachable ({0}ms)", [to_string(to_string(latency_raw))])
      }
    } else {
      cli_message(__cli_locale, "unreachable", [])
    }
    out = out + "  " + name_padded + " " + configured + ", " + reachable + "\n"
    const errors = safe_list(provider["errors"])
    for error in errors {
      if type_of(error) == "string" {
        out = out + "       " + error + "\n"
      }
    }
  }
  return out
}

fn __render_capabilities_section(__cli_locale: string, capabilities: list) -> string {
  let out = cli_message(__cli_locale, "--- Stdlib capabilities ---\n", [])
  for raw_capability in capabilities {
    const capability = safe_dict(raw_capability)
    const name = safe_string(capability["name"], "")
    const name_padded = str_pad(name, 24, " ", "right")
    const profiles = safe_list(capability["available_in_sandbox_profile"])
    out = cli_message(
      __cli_locale,
      "{0}  {1} sandbox: {2}\n",
      [to_string(out), to_string(name_padded), to_string(join(profiles, ", "))],
    )
  }
  return out
}

fn __render_summary_section(__cli_locale: string, summary: dict) -> string {
  let out = cli_message(__cli_locale, "--- Summary ---\n", [])
  const ok_count = safe_int(summary["ok"], 0)
  const warn_count = safe_int(summary["warning"], 0)
  const fail_count = safe_int(summary["blocking"], 0)
  const skip_count = safe_int(summary["skip"], 0)
  out = cli_message(
    __cli_locale,
    "{0}OK={1} WARN={2} FAIL={3} SKIP={4}\n",
    [
      to_string(out),
      to_string(to_string(ok_count)),
      to_string(to_string(warn_count)),
      to_string(to_string(fail_count)),
      to_string(to_string(skip_count)),
    ],
  )
  const blocked = safe_list(summary["blocked_flows"])
  if len(blocked) > 0 {
    out = cli_message(
      __cli_locale,
      "{0}blocked: {1}\n",
      [to_string(out), to_string(join(blocked, ", "))],
    )
  }
  return out
}

/**
 * Render the full human-readable report. Layout follows the host
 * formatter:
 *   `Harn doctor\n`
 *   `\n`
 *   <checks>...
 *   `\n`
 *   `--- Targets ---\n` ...
 *   `\n`
 *   `--- Providers ---\n` ...
 *   `\n`
 *   `--- Stdlib capabilities ---\n` ...
 *   `\n`
 *   `--- Summary ---\n` ...
 *   `\n`
 *   `--- Next step ---\n`
 *   <next_step>
 */
fn __render_human(__cli_locale: string, report: dict) -> string {
  let out = cli_message(__cli_locale, "Harn doctor\n\n", [])
  out = out + __render_checks_section(__cli_locale, safe_list(report["checks"]))
  out = out + "\n"
  out = out + __render_targets_section(__cli_locale, safe_list(report["targets"]))
  out = out + "\n"
  out = out + __render_providers_section(__cli_locale, safe_list(report["providers"]))
  out = out + "\n"
  out = out + __render_capabilities_section(__cli_locale, safe_list(report["capabilities"]))
  out = out + "\n"
  out = out + __render_summary_section(__cli_locale, safe_dict(report["summary"]))
  out = out + "\n"
  out = cli_message(__cli_locale, "{0}--- Next step ---\n", [to_string(out)])
  out = out + safe_string(report["next_step"], "")
  return out
}

fn main(harness: Harness) -> int {
  const __cli_locale = cli_locale(harness.env)
  const raw = harness.env.get_or("HARN_DOCTOR_REPORT_JSON", "")
  if raw == "" {
    harness.stdio.eprintln("internal error: HARN_DOCTOR_REPORT_JSON not set by dispatch shim")
    return 70
  }
  const report = try {
    json_parse(raw)
  } catch (e) {
    harness.stdio.eprintln("internal error: failed to parse doctor report: " + e.message)
    return 70
  }
  if type_of(report) != "dict" {
    harness.stdio.eprintln("internal error: doctor report must be a JSON object")
    return 70
  }
  const json_mode = harness.env.get_or("HARN_OUTPUT_JSON", "0") == "1"
  if json_mode {
    // Use the pre-serialised envelope from the Rust shim verbatim so
    // the byte stream keeps serde's declaration-order field layout.
    // Re-serialising via
    // `json_stringify_pretty` here would alphabetise the keys.
    const envelope_raw = harness.env.get_or("HARN_DOCTOR_REPORT_ENVELOPE_JSON", "")
    if envelope_raw == "" {
      harness.stdio.eprintln(
        "internal error: HARN_DOCTOR_REPORT_ENVELOPE_JSON not set by dispatch shim",
      )
      return 70
    }
    harness.stdio.println(envelope_raw)
  } else {
    // The human renderer should end with one trailing newline.
    // `__render_human` does not add that newline itself; let
    // `harness.stdio.println` add exactly one.
    harness.stdio.println(__render_human(__cli_locale, report))
  }
  const summary = safe_dict(report["summary"])
  const blocking = safe_int(summary["blocking"], 0)
  if blocking > 0 {
    return 1
  }
  return 0
}