Phase Type Definitions
Workflow phases (spec, exec, qa, etc.) are defined once in the phase registry and exposed through a Zod refinement schema. All modules that need the Phase type import from a single source.
Prerequisites
Section titled “Prerequisites”- Zod — already a project dependency (
import { z } from "zod")
Where Phase Is Defined
Section titled “Where Phase Is Defined”Two cooperating files in src/lib/workflow/:
phase-registry.ts— the registry singleton plus the built-inphaseRegistry.register(...)calls at the bottom of the file (canonical pipeline order).types.ts— exportsPhaseSchema(a Zod string refinement backed byphaseRegistry.has(name)) and thePhasetype alias (string).
import { phaseRegistry, getPhaseNames } from "./phase-registry.js";
export const PhaseSchema = z .string() .refine((name) => phaseRegistry.has(name), { error: (issue) => `Unknown phase "${String(issue.input)}". Available: ${getPhaseNames().join(", ")}`, });
export type Phase = string;Two other files re-export it (they do not define their own copy):
| File | Re-exports | Also exports |
|---|---|---|
state-schema.ts | PhaseSchema, Phase | WORKFLOW_PHASES (derived from getPhaseNames()) |
run-log-schema.ts | PhaseSchema, Phase | — |
Adding a New Phase
Section titled “Adding a New Phase”- Add a
phaseRegistry.register({ ... })call in the built-in section at the bottom ofsrc/lib/workflow/phase-registry.ts. Required fields:name,skill,promptTemplate,requiresWorktree. Optional:retryStrategy,detect,driverOverrides,order. - Insertion order in
phase-registry.tsIS the canonical pipeline order — place the new entry where it should run. - Run
npm run build— the build will pass becausePhaseis nowstring; there are noRecord<Phase, ...>maps left to update. - Run
npx vitest run src/lib/workflow/phase-types.test.tsto verify schema identity and registry consistency.
No changes needed in types.ts, state-schema.ts, or run-log-schema.ts — they read from the registry automatically.
Verdict Types
Section titled “Verdict Types”Merge-check verdict types still follow the simpler typed-enum pattern in src/lib/merge-check/types.ts:
export const CHECK_VERDICTS = ["PASS", "WARN", "FAIL"] as const;export const CheckVerdictSchema = z.enum(CHECK_VERDICTS);export type CheckVerdict = z.infer<typeof CheckVerdictSchema>;The phase registry pattern is only used for Phase because phases carry per-entry metadata (prompts, retry strategy, detect rules) that a typed-enum cannot express.
What to Expect
Section titled “What to Expect”getPhaseNames()(fromphase-registry.ts) returns all registered phase names in insertion order. This replaces the priorPhaseSchema.optionsarray.phaseRegistry.get(name)returns the fullPhaseDefinition(prompts, worktree flag, etc.); throws with a “did you mean” list on unknown names.PhaseSchema.safeParse(value)validates a string at runtime against the registry.WORKFLOW_PHASESinstate-schema.tsisgetPhaseNames()(a snapshot of the registry).
Troubleshooting
Section titled “Troubleshooting”Runtime error: Unknown phase "..."
Section titled “Runtime error: Unknown phase "..."”Symptoms: PhaseSchema.parse(name) or phaseRegistry.get(name) throws with the registered-names list.
Solution: Either typo in the phase name, or a new phase wasn’t registered. Confirm a phaseRegistry.register(...) call exists in phase-registry.ts for the name.
Test failure: phase-types.test.ts identity check
Section titled “Test failure: phase-types.test.ts identity check”Symptoms: Test fails with “expected X toBe Y” or similar.
Solution: A file is defining its own PhaseSchema instead of importing from types.ts, or registering a phase twice. Check for independent z.enum/z.string definitions containing "spec", and check for duplicate phaseRegistry.register({ name: "..." }) calls.
Generated for Issue #401 on 2026-03-25. Updated for Issue #505 (registry migration).