harn-stdlib 0.8.52

Embedded Harn standard library source catalog
Documentation
/**
 * `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 matches the legacy `serde_json::to_string_pretty`
 *     output (declaration-order field layout, `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.
 */
fn __safe_string(value, fallback: string) -> string {
  if type_of(value) == "string" {
    return value
  }
  return fallback
}

fn __safe_list(value) -> list {
  if type_of(value) == "list" {
    return value
  }
  return []
}

fn __safe_dict(value) -> dict {
  if type_of(value) == "dict" {
    return value
  }
  return {}
}

fn __safe_bool(value, fallback: bool) -> bool {
  if type_of(value) == "bool" {
    return value
  }
  return fallback
}

fn __safe_int(value, fallback: int) -> int {
  if type_of(value) == "int" {
    return value
  }
  return fallback
}

/**
 * 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(status: string) -> string {
  if status == "ok" {
    return "OK"
  }
  if status == "warn" {
    return "WARN"
  }
  if status == "fail" {
    return "FAIL"
  }
  if status == "skip" {
    return "SKIP"
  }
  return status
}

/**
 * Render the `checks:` section. Layout matches the legacy Rust impl:
 *   `{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(checks: list) -> string {
  var out = ""
  for raw_check in checks {
    let check = __safe_dict(raw_check)
    let status = __safe_string(check["status"], "")
    let label = __safe_string(check["label"], "")
    let detail = __safe_string(check["detail"], "")
    let status_upper = __status_upper(status)
    let status_padded = str_pad(status_upper, 4, " ", "left")
    let label_padded = str_pad(label, 24, " ", "right")
    out = out + status_padded + "  " + label_padded + " " + detail + "\n"
    if status != "ok" && status != "skip" {
      let fix = check["fix_command"]
      if type_of(fix) == "string" && fix != "" {
        out = out + "       fix: " + fix + "\n"
      }
      let docs = check["docs_url"]
      if type_of(docs) == "string" && docs != "" {
        out = out + "       docs: " + docs + "\n"
      }
      let blocks = __safe_list(check["blocks"])
      if len(blocks) > 0 {
        out = out + "       blocks: " + join(blocks, ", ") + "\n"
      }
    }
  }
  return out
}

fn __render_targets_section(targets: list) -> string {
  var out = "--- Targets ---\n"
  for raw_target in targets {
    let target = __safe_dict(raw_target)
    let triple = __safe_string(target["triple"], "")
    let triple_padded = str_pad(triple, 32, " ", "right")
    let installed = if __safe_bool(target["installed"], false) {
      "installed"
    } else {
      "missing"
    }
    let buildable_raw = target["buildable"]
    let buildable = if buildable_raw == nil {
      "not probed"
    } else if __safe_bool(buildable_raw, false) {
      "buildable"
    } else {
      "not buildable"
    }
    out = out + "  " + triple_padded + " " + installed + ", " + buildable + "\n"
    let reasons = __safe_list(target["reasons"])
    for reason in reasons {
      if type_of(reason) == "string" {
        out = out + "       " + reason + "\n"
      }
    }
  }
  return out
}

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

fn __render_capabilities_section(capabilities: list) -> string {
  var out = "--- Stdlib capabilities ---\n"
  for raw_capability in capabilities {
    let capability = __safe_dict(raw_capability)
    let name = __safe_string(capability["name"], "")
    let name_padded = str_pad(name, 24, " ", "right")
    let profiles = __safe_list(capability["available_in_sandbox_profile"])
    out = out + "  " + name_padded + " sandbox: " + join(profiles, ", ") + "\n"
  }
  return out
}

fn __render_summary_section(summary: dict) -> string {
  var out = "--- Summary ---\n"
  let ok_count = __safe_int(summary["ok"], 0)
  let warn_count = __safe_int(summary["warning"], 0)
  let fail_count = __safe_int(summary["blocking"], 0)
  let skip_count = __safe_int(summary["skip"], 0)
  out = out + "OK=" + to_string(ok_count)
    + " WARN="
    + to_string(warn_count)
    + " FAIL="
    + to_string(fail_count)
    + " SKIP="
    + to_string(skip_count)
    + "\n"
  let blocked = __safe_list(summary["blocked_flows"])
  if len(blocked) > 0 {
    out = out + "blocked: " + join(blocked, ", ") + "\n"
  }
  return out
}

/**
 * Render the full human-readable report. Layout follows the legacy
 * Rust impl byte-for-byte:
 *   `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(report: dict) -> string {
  var out = "Harn doctor\n\n"
  out = out + __render_checks_section(__safe_list(report["checks"]))
  out = out + "\n"
  out = out + __render_targets_section(__safe_list(report["targets"]))
  out = out + "\n"
  out = out + __render_providers_section(__safe_list(report["providers"]))
  out = out + "\n"
  out = out + __render_capabilities_section(__safe_list(report["capabilities"]))
  out = out + "\n"
  out = out + __render_summary_section(__safe_dict(report["summary"]))
  out = out + "\n"
  out = out + "--- Next step ---\n"
  out = out + __safe_string(report["next_step"], "")
  return out
}

fn main(harness: Harness) -> int {
  let 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
  }
  let report = try {
    json_parse(raw)
  } catch (e) {
    harness.stdio.eprintln("internal error: failed to parse doctor report: " + to_string(e))
    return 70
  }
  if type_of(report) != "dict" {
    harness.stdio.eprintln("internal error: doctor report must be a JSON object")
    return 70
  }
  let 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 matches the legacy `serde_json::to_string_pretty`
    // output (declaration-order field layout). Re-serialising via
    // `json_stringify_pretty` here would alphabetise the keys.
    let 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 legacy renderer ends with a single `println!("{next_step}")`
    // so the buffer ends with one trailing newline. `__render_human`
    // does not add that newline itself; let `harness.stdio.println`
    // add exactly one to match.
    harness.stdio.println(__render_human(report))
  }
  let summary = __safe_dict(report["summary"])
  let blocking = __safe_int(summary["blocking"], 0)
  if blocking > 0 {
    return 1
  }
  return 0
}