initial import: @mikefreno/omp-pygenium (omp port)
This commit is contained in:
108
src/status.ts
Normal file
108
src/status.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* status.ts — readable run-state formatter.
|
||||
*
|
||||
* {@link formatRunStatus} turns a `RunState` into a plain line list covering
|
||||
* the run-level summary and one block per registered check: overall status, the
|
||||
* per-phase breakdown, captured findings/changes artifacts, and any errors.
|
||||
* It is a *pure* function of state — no disk I/O — so it is trivially
|
||||
* unit-testable and deterministic; the in-memory run-state is the single source
|
||||
* of truth for progress (the check-runner records findings/changes text on it).
|
||||
*
|
||||
* `formatRunStatus` is the single helper the `/pygienium-status` command uses;
|
||||
* keeping it here (out of `commands.ts`) lets `commands.ts` stay a thin binder.
|
||||
*
|
||||
* @module pygienium/status
|
||||
*/
|
||||
|
||||
import type { CheckRun, RunState } from "./run-state.js";
|
||||
|
||||
const PHASE_ORDER = ["recon", "analysis", "fix", "verify", "cleanup"] as const;
|
||||
|
||||
function toISO(ms: number | undefined): string {
|
||||
return ms == null ? "—" : new Date(ms).toISOString();
|
||||
}
|
||||
|
||||
function short(status: string): string {
|
||||
return status[0]?.toUpperCase() ?? "?";
|
||||
}
|
||||
|
||||
/** Count non-empty lines in captured findings/changes text. */
|
||||
function lineCount(text: string | undefined): number {
|
||||
if (!text) return 0;
|
||||
const count = text.split(/\r?\n/).filter((l) => l.trim().length > 0).length;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a single check block (without a trailing separator) — exposed so tests
|
||||
* and the status command share one rendering path.
|
||||
*/
|
||||
export function formatCheckBlock(check: CheckRun, indent = " "): string[] {
|
||||
const lines: string[] = [];
|
||||
const flag = check.fix ? " (--fix)" : "";
|
||||
lines.push(`${indent}${check.name} — ${check.status}${flag}`);
|
||||
|
||||
const phaseSummary = check.phases
|
||||
.map((p) => `${p.id}:${p.status.startsWith("in_progress") ? "…" : short(p.status)}`)
|
||||
.join(" ");
|
||||
if (phaseSummary) lines.push(`${indent} phases: ${phaseSummary}`);
|
||||
|
||||
const findingsLines = lineCount(check.findings);
|
||||
if (findingsLines > 0) {
|
||||
lines.push(`${indent} findings: ${findingsLines} line(s)`);
|
||||
}
|
||||
const changesLines = lineCount(check.changes);
|
||||
if (changesLines > 0) {
|
||||
lines.push(`${indent} changes: ${changesLines} line(s)`);
|
||||
}
|
||||
|
||||
if (check.error) {
|
||||
lines.push(`${indent} error: ${check.error}`);
|
||||
}
|
||||
for (const phase of check.phases) {
|
||||
if (phase.status === "failed" && phase.error && phase.error !== check.error) {
|
||||
lines.push(`${indent} ${phase.id}: ${phase.error}`);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the `/pygienium-status` line list for a run state. Pure: no disk reads.
|
||||
* Layout:
|
||||
*
|
||||
* pygienium run — <status>
|
||||
* started: <iso>
|
||||
* updated: <iso>
|
||||
* cwd: <cwd>
|
||||
* recon: complete|pending
|
||||
*
|
||||
* checks (N):
|
||||
* <name> — <status>
|
||||
* phases: recon:✓ analysis:✓ [fix:✓] verify:✓ cleanup:✓
|
||||
* findings: <N> line(s)
|
||||
* changes: <N> line(s)
|
||||
* error: <msg>
|
||||
*/
|
||||
export function formatRunStatus(state: RunState): string[] {
|
||||
const checks = Object.values(state.checks);
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push(`pygienium run — ${state.status}`);
|
||||
lines.push(` started: ${toISO(state.startedAt)}`);
|
||||
lines.push(` updated: ${toISO(state.updatedAt)}`);
|
||||
lines.push(` cwd: ${state.cwd}`);
|
||||
const reconLabel = state.recon.complete ? "complete" : "pending";
|
||||
const reconTime = state.recon.finishedAt ? ` (${toISO(state.recon.finishedAt)})` : "";
|
||||
lines.push(` recon: ${reconLabel}${reconTime}`);
|
||||
lines.push("");
|
||||
|
||||
lines.push(` checks (${checks.length}):`);
|
||||
if (checks.length === 0) {
|
||||
lines.push(" (none registered in this run state)");
|
||||
}
|
||||
for (const check of checks) {
|
||||
lines.push(...formatCheckBlock(check));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
Reference in New Issue
Block a user