/* RFC-0477: sternsystem.status — read-only synchronization state inspection for a Sternsystem. Does not mutate state — read-only diagnostic command. Does not check bordbuch hash-chain integrity — that is bordbuch.validate. Does not show deployment status — that is leitstand.status. Does not perform live network calls to the mirror — reads local refs only. RFC-0477: initial sternsystem.status command handler. RFC-1063: add currentMission from system-state.yaml to status output. RFC-1149: migrate ambient filesystem and process IO to the WorkspaceIO port across engine and shared RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt */ import { gitExec } from "../werkstatt/git-exec.ts"; import type { KernelCommandInput, KernelCommandResult, KernelRuntimeContext, } from "@warpgogol/werkstatt-engine/kernel"; import type { BordbuchEntry } from "@warpgogol/werkstatt-engine/schemas"; import { readSystemConfig, readSystemState, resolveMirrors, resolveMirrorPath, resolveCacheClonePath, discoverSystems, } from "./registry-io.ts"; import { readBordbuch } from "../bordbuch/bordbuch-io.ts"; import { readMissionManifest } from "../mission/mission-io.ts"; export interface SternsystemStatusGit { headSha: string | null; originSha: string | null; mirrorSha: string | null; headVsOrigin: "sync" | "behind" | "ahead" | "diverged" | "unknown"; originVsMirror: "sync" | "behind" | "ahead" | "diverged" | "unknown"; dirtyFiles: string[]; } export interface SternsystemStatusBordbuch { lastEvents: BordbuchEntry[]; totalEvents: number; } export interface SternsystemStatusLastMission { missionId: string | null; state: string; reconciledAt: string | null; } export interface SternsystemStatusCurrentMission { missionId: string; brief: string; state: string; openedAt: string; openedBy: string; } export interface SternsystemStatusData { systemId: string; currentMission: SternsystemStatusCurrentMission | null; git: SternsystemStatusGit; bordbuch: SternsystemStatusBordbuch; lastMission: SternsystemStatusLastMission | null; } function flagString(input: KernelCommandInput, key: string): string | undefined { const v = input.flags[key]; return typeof v === "string" ? v : undefined; } function flagBoolean(input: KernelCommandInput, key: string): boolean { const v = input.flags[key]; return v === true || v === "true"; } function compareSha( a: string | null, b: string | null, ): "sync" | "behind" | "ahead" | "diverged" | "unknown" { if (!a || !b) return "unknown"; if (a === b) return "sync"; return "unknown"; } async function statusForSystem( workspaceRoot: string, systemId: string, io: import("@warpgogol/werkstatt-shared/kernel/workspace-io").WorkspaceIO, ): Promise { let config: Awaited> | null = null; try { config = await readSystemConfig(workspaceRoot, systemId); } catch { config = null; } const systemDir = config ? resolveMirrors(workspaceRoot, config).cachePath : resolveCacheClonePath(workspaceRoot, systemId); // Git SHAs let headSha: string | null = null; let originSha: string | null = null; let mirrorSha: string | null = null; let dirtyFiles: string[] = []; if (await io.exists(systemDir)) { try { headSha = await gitExec(systemDir, "rev-parse HEAD", undefined, io); } catch { headSha = null; } try { dirtyFiles = (await gitExec(systemDir, "status --porcelain", undefined, io)) .split("\n") .filter((l) => l.trim().length > 0) .map((l) => l.slice(3)); } catch { dirtyFiles = []; } } if (config && config.mirrors.length > 1) { const bareMirror = config.mirrors[1]; const bareRepoPath = resolveMirrorPath(workspaceRoot, bareMirror.path); if (await io.exists(bareRepoPath)) { let branch: string; try { branch = (await gitExec(bareRepoPath, "symbolic-ref HEAD", undefined, io)).replace( "refs/heads/", "", ); } catch { branch = "main"; } try { originSha = await gitExec(bareRepoPath, `rev-parse ${branch}`, undefined, io); } catch { originSha = null; } if (config.mirrors.length > 2) { try { mirrorSha = await gitExec(bareRepoPath, `rev-parse refs/mirror/${branch}`, undefined, io); } catch { mirrorSha = null; } } } } // Current mission from system-state.yaml (authoritative) let currentMission: SternsystemStatusCurrentMission | null = null; try { const state = await readSystemState(workspaceRoot, systemId); if (state.currentMission) { try { const manifest = await readMissionManifest(workspaceRoot, state.currentMission); currentMission = { missionId: state.currentMission, brief: manifest.brief, state: manifest.state, openedAt: manifest.openedAt, openedBy: manifest.openedBy, }; } catch { currentMission = { missionId: state.currentMission, brief: "unknown", state: "unknown", openedAt: "", openedBy: "", }; } } } catch { currentMission = null; } // Bordbuch events const entries = await readBordbuch(workspaceRoot, systemId); const lastEvents = entries.slice(-6); const totalEvents = entries.length; // Last mission from bordbuch let lastMission: SternsystemStatusLastMission | null = null; let lastMissionId: string | null = null; for (let i = entries.length - 1; i >= 0; i--) { if ( entries[i].kind === "mission-open" || entries[i].kind === "mission-close" || entries[i].kind === "mission-abort" ) { lastMissionId = entries[i].missionId ?? null; break; } } if (lastMissionId) { try { const manifest = await readMissionManifest(workspaceRoot, lastMissionId); lastMission = { missionId: lastMissionId, state: manifest.state, reconciledAt: manifest.reconciledAt, }; } catch { lastMission = { missionId: lastMissionId, state: "unknown", reconciledAt: null, }; } } return { systemId, currentMission, git: { headSha, originSha, mirrorSha, headVsOrigin: compareSha(headSha, originSha), originVsMirror: compareSha(originSha, mirrorSha), dirtyFiles, }, bordbuch: { lastEvents, totalEvents, }, lastMission, }; } export async function runSternsystemStatus( input: KernelCommandInput, context: KernelRuntimeContext, ): Promise> { const { workspaceRoot } = context; const id = flagString(input, "id"); const all = flagBoolean(input, "all"); if (!id && !all) { throw new Error("[sternsystem.status] --id is required (or use --all for all systems)"); } if (all) { const { systems: configs } = await discoverSystems(workspaceRoot); const results: SternsystemStatusData[] = []; for (const config of configs) { try { const status = await statusForSystem(workspaceRoot, config.id, context.io); results.push(status); } catch { results.push({ systemId: config.id, currentMission: null, git: { headSha: null, originSha: null, mirrorSha: null, headVsOrigin: "unknown", originVsMirror: "unknown", dirtyFiles: [], }, bordbuch: { lastEvents: [], totalEvents: 0 }, lastMission: null, }); } } return { data: results, exitCode: 0, summary: `[sternsystem.status] reported ${results.length} system(s)`, nextSteps: [ { action: `Sync out-of-date systems: pnpm exec werkstatt run sternsystem.sync --id `, kind: "optional", }, ], }; } const status = await statusForSystem(workspaceRoot, id!, context.io); const mirrorInfo = status.git.originVsMirror === "sync" ? "mirror in sync" : status.git.originVsMirror === "unknown" ? "mirror unknown" : `mirror ${status.git.originVsMirror}`; const missionInfo = status.currentMission ? `mission=${status.currentMission.missionId} (${status.currentMission.state})` : "no active mission"; return { data: status, exitCode: 0, summary: `[sternsystem.status] ${id}: ${missionInfo}, HEAD=${status.git.headVsOrigin} origin, ${mirrorInfo}`, nextSteps: status.git.headVsOrigin !== "sync" || status.git.originVsMirror !== "sync" ? [ { action: `Sync the system: pnpm exec werkstatt run sternsystem.sync --id ${id}`, kind: "optional", }, ] : undefined, }; }