akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
76 lines (75 loc) • 4.1 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
import { flattenForText } from "../generic-render.js";
import { renderStatusEntries, summarizeCounts } from "./status-list.js";
const STATUS_GLYPH = { fail: "✗", warn: "⚠", unknown: "?", pass: "✓" };
const STATUS_RANK = { fail: 0, warn: 1, unknown: 2, pass: 3 };
/** Worst-first, for both the section summary and (via `STATUS_RANK`) row order. */
const STATUS_ORDER = ["fail", "warn", "unknown", "pass"];
/** One evidence field per line, `key: value` — objects/arrays JSON-compact, matching `formatInfoPlain`'s nested-field convention. */
function evidenceLines(evidence) {
if (!evidence)
return [];
return Object.entries(evidence).map(([key, value]) => `${key}: ${value !== null && typeof value === "object" ? JSON.stringify(value) : String(value)}`);
}
function checkStatusEntry(check, detail) {
const status = check.status ?? "unknown";
const meta = [check.kind, check.confidence].filter((v) => !!v).join(", ");
return {
severityRank: STATUS_RANK[status] ?? 9,
glyph: STATUS_GLYPH[status] ?? "?",
headline: `${check.name} [${status}${meta ? `, ${meta}` : ""}] ${check.message ?? ""}`.trimEnd(),
// Evidence is diagnostic detail, not the headline signal — gated behind
// --detail the same way `formatSearchPlain`/`formatCuratePlain` gate
// their own verbose fields (full path/editHint/whyMatched, "why" reasoning).
detailLines: detail === "brief" ? [] : evidenceLines(check.evidence),
};
}
/** Render one `hardChecks`/`advisories` section: a count summary header, then worst-first rows. */
function renderCheckSection(title, checks, detail) {
if (checks.length === 0)
return [`${title}: (none)`];
const counts = {};
for (const check of checks) {
const status = check.status ?? "unknown";
counts[status] = (counts[status] ?? 0) + 1;
}
const summary = summarizeCounts(counts, STATUS_ORDER);
const header = `${title} (${checks.length}${summary ? ` — ${summary}` : ""})`;
return [header, ...renderStatusEntries(checks.map((check) => checkStatusEntry(check, detail)))];
}
/** Fields already rendered explicitly above — everything else in `r` falls to the `flattenForText` sweep below. */
const HANDLED_KEYS = new Set(["ok", "status", "since", "hardChecks", "advisories", "shape", "schemaVersion"]);
export function formatHealthPlain(r, detail) {
if (r === null || typeof r !== "object")
return null;
const lines = [];
if (typeof r.ok === "boolean")
lines.push(`ok: ${r.ok}`);
if (typeof r.status === "string")
lines.push(`status: ${r.status}`);
if (typeof r.since === "string")
lines.push(`since: ${r.since}`);
const hardChecks = Array.isArray(r.hardChecks) ? r.hardChecks : [];
const advisories = Array.isArray(r.advisories) ? r.advisories : [];
lines.push("", ...renderCheckSection("hardChecks", hardChecks, detail));
lines.push("", ...renderCheckSection("advisories", advisories, detail));
if (detail === "brief" &&
[...hardChecks, ...advisories].some((c) => c.evidence && Object.keys(c.evidence).length > 0)) {
lines.push("", "(evidence omitted at --detail brief; re-run with --detail normal or --detail full to see it)");
}
// Everything else (metrics, improve, and the optional runs/windows/deltas/
// report datasets) is a nested scalar tree, not the array-of-records shape
// this formatter exists to fix — keep the existing dotted-path convention
// for it rather than inventing a second one.
for (const [key, value] of Object.entries(r)) {
if (HANDLED_KEYS.has(key) || value === undefined)
continue;
const fieldLines = [];
flattenForText(value, key, fieldLines);
if (fieldLines.length > 0)
lines.push("", ...fieldLines);
}
return lines.join("\n").trim();
}