/** * KHAELOR * File: src/phases/state.ts * Description: Pure fold of phase events into the current phase-gate state (v2 design ยง1). * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai */ import type { DesignArtifact, DurableEvent, Phase } from "../session/index.js"; export interface PhaseState { /** Current phase; sessions without any phase event are in "understand". */ phase: Phase; /** Artifact submitted but not yet approved/rejected. */ pendingArtifact: { artifactId: string; artifact: DesignArtifact } | null; /** The artifact id whose approval unlocked implement, when one exists. */ approvedArtifactId: string | null; /** True once a phase.approved{phase:"design"} exists in the session. */ designApproved: boolean; } /** Fold the durable stream into the phase-gate decision state. */ export function foldPhaseState(events: readonly DurableEvent[]): PhaseState { let phase: Phase = "understand"; let pending: PhaseState["pendingArtifact"] = null; let approvedArtifactId: string | null = null; let designApproved = false; for (const event of events) { switch (event.type) { case "phase.entered": phase = event.payload.phase; break; case "phase.artifact": pending = { artifactId: event.payload.artifactId, artifact: event.payload.artifact }; break; case "phase.approved": if (event.payload.phase === "design") { designApproved = true; approvedArtifactId = event.payload.artifactId ?? pending?.artifactId ?? null; pending = null; } break; case "phase.rejected": pending = null; break; default: break; } } return { phase, pendingArtifact: pending, approvedArtifactId, designApproved }; }