Skip to content

Stability Policy ​

This page classifies every exported type by the compatibility contract it carries. Version numbers follow SemVer; the table below defines what counts as breaking for each category.

Categories ​

CategoryExamplesMinor releases may
Sealed framework valuesOut, RenderContext, FlagSchema, ArgSchema, CommandSchemaadd readonly members
Consumer input optionsRunOptions, CLIRunOptions, OutputOptions, RenderContextOptions, HelpOptionsadd optional fields
Implementer interfacesRuntimeAdapter, PromptEngine, CLIPluginadd optional hooks only
Closed unionsVerbosity, ActivityEvent, OutputStreamchange nothing
Internal surfaceunderscore members (_execute, _ctx), Internal* option typeschange anything, any release

Sealed framework values ​

Out and RenderContext are framework-created, non-exhaustive values. Both carry a private brand, so implementing or structurally constructing them outside the framework does not type-check.

  • Obtain instances from action parameters, createOutput(), createCaptureOutput(), or resolveRenderContext().
  • New readonly members may appear in minor releases. Code that only reads documented members keeps compiling.
  • Do not enumerate their keys exhaustively (Record<keyof Out, ...>); a minor release may add a key.
  • Helpers that need a subset should accept a capability type:
ts
type InformationalOutput = Pick<Out, 'info' | 'status'>;

function renderRows(out: InformationalOutput, rows: readonly string[]) {
	for (const row of rows) out.info(row);
}

For test doubles, take a real channel from the testkit and spy on it:

ts
const [out] = createCaptureOutput();
vi.spyOn(out, 'info');

FlagSchema, ArgSchema, and CommandSchema carry the same brand. Build them with createFlagSchema(), createArgSchema(), and createCommandSchema(), which take a definition object and return the normalized schema:

ts
const deploy = createCommandSchema({
	name: 'deploy',
	flags: { force: { kind: 'boolean' } },
	args: [{ name: 'target', schema: { kind: 'string' } }],
});
  • Spreading a built schema keeps the brand, so { ...deploy, hidden: true } stays assignable to CommandSchema.
  • Feeding a built schema back through its factory produces a deep-equal schema.
  • A field belonging to another kind (enumValues on a boolean flag) fails to compile and throws INVALID_SCHEMA at runtime.

Consumer input options ​

Option objects the caller constructs and passes in. All fields are optional; minor releases may add optional fields but never required ones, and never change the meaning of an existing field.

Implementer interfaces ​

Interfaces designed to be implemented outside the framework. Their required surface is frozen within a major version; new capabilities arrive as optional members. A redesign ships as a new interface, with the old one supported until the next major.

The low-level output seam is the injected stdout/stderr writer pair on OutputOptions (WriteFn), not a reimplementation of Out.

Closed unions ​

String and discriminated unions are closed: consumers may switch exhaustively over them (satisfies never in the default branch), and adding a variant is a breaking change released in a major.

Internal surface ​

Members prefixed with an underscore and types named Internal* exist for the framework's own layering and for in-repo tests. They may change or disappear in any release. The @internal TSDoc tag marks the same boundary on individual members.

Released under the MIT License.