These principles are drawn from the current rebuild and earlier design
transcripts. Current decisions take precedence over superseded architectures.
★ marks principles that received especially enthusiastic assent.
The detailed phase-binding design is recorded in Binding.
- ★ Sketch the ideal API first; write exact type expectations second; test third; implement last.
- ★ Hover types are part of the product.
- The primary generic represents the actual runtime value, not a definition tree or helper-type calculation.
- Definitions should read like configuration, not "config balls."
- Definitions are immutable function-composition pipelines.
- Any valid
A → Btransformation may participate in a pipeline. extend()packages context-free composition;extend()is identity.- Static built-in route elements carry a hidden operation type and fold in bounded chunks; their number is not limited by an overload tower.
- Macros composed with
extend()inherit that operation type without an explicit registration step. Concrete unbrandedA → Bfunctions compose directly; bespoke generic atoms use an explicit type-lambda marker because TypeScript cannot otherwise apply their input-dependent output type. - Static definitions contain enough metadata to render help and schemas without parsing input.
- Standard Schema validates values; it does not define source syntax or token grammar.
- Complexity may exist behind overloads and implementation-boundary casts, but not in user-facing types.
- ★ Resolve the route before interpreting remaining tokens as parameters.
- The program name is identity, not part of its route:
/,/serve,/database/clean. - Command literals are routing tokens, not positional arguments.
- Every route is a distinct application entry point.
- Every route supports
HELP. VERSIONandEXECUTEexist only where declared.command()is exactly an executableroute().- An executable route may still contain child routes.
- Controls are semantic methods, not special tokenizer token types.
- The deepest discoverable route owns the requested method.
- Control placement does not change the route:
app --help childandapp child --helptarget the child. - Nothing after
--participates in routing, controls, or option binding. - There is always at least the root route; unmatched words become binding overflow rather than "route not found."
- Providers declare local names; mounting supplies route, namespace, and option prefixes.
- Tokenization describes syntax only; bindings assign semantic meaning.
- Input is immutable.
- Tokens retain stable global indices and original order.
- Claims never make originally non-adjacent tokens adjacent.
- Parents establish scope; children claim only within that scope.
- Available input flows down; claims, issues, and models flow up.
- Claims plus remainder conserve the original input: nothing disappears or is claimed twice.
- Unknown tokens are diagnosed only after all applicable bindings have had an opportunity to claim them.
- The original CLI input is supplied once; continuations retain its unconsumed portion.
- ★ A parameter introduces an address and a target value type.
- A binding reads a source's physical representation.
- A decoder interprets a captured representation.
- A schema validates the interpreted model value.
- ★ Binding failure means the source representation could not be captured.
- ★ Decoding failure means it was captured but could not be interpreted.
- ★ Schema failure means it was interpreted but is not a valid model value.
- CLI, environment, and JavaScript values have source-specific readers.
- CLI and environment readers may share decoders without sharing capture semantics.
switch()owns boolean CLI grammar; schemas are never probed to infer token behavior.- Source selection follows explicit precedence.
- A valid higher-priority winner is not invalidated by a bad shadowed source.
- ★ Shadowed invalid sources should remain available as diagnostics.
- A child configuration is validated through the child's value binding, not round-tripped through argv.
- The resolved route has a directly typed
model. - The result also carries path-addressed
modelsfor every matched route. - A route's phase tuple is the single source of truth for both its aggregate model and its children; public helpers derive those views instead of caching them as additional route type parameters.
- Route identity and result projection are separate concerns.
- Namespacing is a mounting/address operation, not something plugin authors repeat locally.
- Exact route and method values discriminate the result union.
- ★ Dynamic parsing is a typed pause, not baked-in discovery or asynchronous loading.
- Library parsing remains synchronous and pure; the application performs I/O between increments.
- Every route has at least one phase.
- Parameters belong to phases and are bound phase by phase.
- Phase parameter types and increment models are phase-local slices.
- A caller has already observed earlier increments; the final intent exposes the aggregate model.
- Once a phase is resolved, its values are immutable.
- Newly supplied values or environment sources apply only to future phases.
- A dynamic resolver returns an extension at its current pipeline position.
- The extension's return type determines the continuation type.
resume()continues parsing; it never exposes the intermediate route.dynamic(schema, extension)requires a Standard Schema for its requirement.resume()accepts the schema's input directly; synchronous validation supplies the schema's output to the extension, including transforms.- An invalid requirement returns
unprocessable-contentwith the schema's issues and never invokes the extension. Async schemas are rejected through the same issue path; the application handles I/O failures before resuming. RequirementsOf<R>preserves requirement order;RequirementOf<R>is its head.ContinuationOf<R>settles the current unresolved phase and retains it as immutable type history, so later continuations can derive the aggregate model without a separate model cache.- The static result type says whether parsing yields another increment or an
intent; callers should not need a runtime
donecheck. - Help and version may require resolving earlier phases when those phases can introduce the target route.
- ★ Help should feel free at every route depth.
printHelp()accepts a Help intent and returns a string; it performs no I/O.printVersion()likewise renders rather than writes.- Help, version, and execute share route resolution.
- Unsupported methods produce method-not-allowed, not malformed pseudo-intents.
- Issues should share one rich, normalized shape compatible with Standard Schema issues.
- Help may expose current values, winning sources, and shadowed invalid sources.
- Help intent normally precedes ordinary argument validation, except for phases required to discover the requested route.
- ★ Behavioral tests should describe public parses, not internal composition machinery.
- Type tests exist where inference itself is the behavior.
- CLI-string fixtures and matchers such as
toHaveRoute()andtoHaveModels()should make intent visible. - Do not test private search segments directly.
- Avoid loops and helper indirection when two literal assertions communicate the contract better.
These have not been promoted to invariants:
- Exact shadowed-source warning policy.
- Holistic model transforms.
- Final alias syntax.
- Recursive-dynamic implementation details.
- The exact route, path, and path-addressed model context exposed by an increment.
- Recursive result typing for increments reached through child routes.
- Every edge of strict versus permissive option placement.