initial import: @mikefreno/omp-pygenium (omp port)

This commit is contained in:
2026-08-10 09:46:09 -04:00
commit a40cdcd9e3
70 changed files with 12624 additions and 0 deletions

108
src/status.ts Normal file
View 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;
}