/*
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,
};
}