# Capabilities

One line per custom operation on the four builders, per `Input` combinator, and per `Cmd` argument helper. The
[API reference](https://shayanhabibi.github.io/Partas.Build/reference/) has full signatures and remarks.
[Composing reusable blocks](composition.md) has worked examples.

## How settings resolve

A stage setting resolves by walking upward: the stage, its parent stage, the parent's parent, then the
pipeline. The first level that sets it wins, so a pipeline's `workingDir` covers every stage under it, and a
nested stage overrides it for itself and its children. This applies to `workingDir`, `envVars`, the timeouts,
`acceptExitCodes`, the output sink, `noPrefixForStep`, `noStdRedirectForStep` and `verbosity`.

Conditions are the exception. `when'`, `whenEnvVar`, `whenBranch` and the platform operations **conjoin**: a
second condition on the same stage narrows it to the logical AND of both. Use `whenAny { }` to widen.

A command's copies of the pipeline settings are **defaults**, not overrides: they reach every pipeline the
command runs, but only where that pipeline left the setting alone, regardless of write order.
`noPrefixForStep` and `noStdRedirectForStep` are plain bools with no unset state, so a pipeline setting either
one to the value `PipelineContext.create` already gives it is indistinguishable from leaving it untouched — the
command default overwrites it either way.

## GitHub Actions reporting

With `GITHUB_ACTIONS=true`, each active top-level stage opens a collapsible log group and closes it even if
the stage fails or is cancelled. Nested stages and parallel work share that group, so concurrent branches do
not open overlapping groups. Quiet pipelines emit no group framing. Stage output captures and redirects
continue to control step output; workflow group commands go directly to the runner's console.

Invoking a `command` or `rootCommand` also appends a Markdown stage timing report to `GITHUB_STEP_SUMMARY`
after each pipeline, including failures and quiet runs. It lists every recorded stage in tree order with
its outcome and elapsed time; skipped stages show no duration. Names and failure details are escaped for
Markdown. Single-stage runs are included, and multiple pipelines append rather than replacing earlier
content. An unavailable summary file or a report that would exceed GitHub's 1 MiB per-step limit prints a
diagnostic without changing the pipeline result. A direct `PipelineContext.run` call groups its logs but
does not write a summary.

Both features are automatic in GitHub Actions and leave local console reporting unchanged.

### Structured annotations

`annotate` turns an `Annotation` into a deferred `Operation<unit>`, used through `runOperation` or composed
with other operations. `Annotation.notice message`, `Annotation.warning message` and `Annotation.error message`
create diagnostics without metadata. Add `Title`, `File`, `Line`, `EndLine`, `Column` and `EndColumn` through
a record update; these fields take `ValueSome` when present. Use repository-relative file paths and one-based
line/column numbers. `EndLine` requires `Line`, columns require `Line`, and `EndColumn` requires `Column`.
Columns describe ranges within one line; omit them for multiline ranges. Nonpositive positions, reversed
ranges or unsupported field combinations raise an argument error when the operation runs.

```fsharp
stage "check configuration" {
    runOperation (annotate {
        Annotation.warning "Use the new option name." with
            Title = ValueSome "Deprecated option"
            File = ValueSome "build.fsx"
            Line = ValueSome 12
            Column = ValueSome 5
            EndColumn = ValueSome 18
    })
}
```

In GitHub Actions these become runner annotations. Locally they print the level, metadata and literal message.
The stage's inherited `GITHUB_ACTIONS` setting selects the format; a stage can override it. Annotations always
go to the diagnostic console, independently of captures, redirects and `quiet`. Constructing an operation or
running `--explain` emits nothing. An error annotation alone does not fail a stage: return a failure or raise
an exception when execution should fail. Compiler logs are not automatically parsed for source locations.

The runner's own stage and pipeline errors use the same protocol writer. Titles and messages are escaped
separately, and commands bypass console wrapping so long diagnostics remain one physical protocol line.
Protocol I/O failures do not change the stage result.

## Timeouts

Three names. Meaning shifts with the builder they sit on.

| Builder | `timeout` | `timeoutForStage` | `timeoutForStep` |
|---|---|---|---|
| `stage` | this stage as a whole | — | each step of this stage |
| `pipeline` | the whole pipeline run | each stage's default | each step's default |
| `command` / `rootCommand` | pipeline default for the whole run | pipeline default for each stage | pipeline default for each step |

The unit differs by builder:
- `pipeline` — `int<second>`, `float` seconds or a `TimeSpan`
- `stage` — plain `int` seconds, `float` seconds or a `TimeSpan`
- `command` / `rootCommand` — `int` seconds or a `TimeSpan`

## Stage operations

Available inside `stage`, and inside `whenStage`, which accepts everything `stage` does.

| Operation | What it does |
|---|---|
| `run` | Adds a step. Takes a literal command line, a `Cmd`, or a function of the `StageContext` returning `unit`, `int`, `Result<unit, string>`, a `Cmd`, an `Async<_>` or a `Task<_>` of any of those, optionally wrapped in `option`. A function returning a `string` is obsolete: use `runLine` |
| `runLine` | Adds a step that runs the command line a function of the `StageContext` returns (`string`, `Async<string>` or `Task<string>`, optionally wrapped in `option`). The line is split on whitespace, honouring quotes |
| `runSensitive` | Adds a step from an interpolated command line with every hole masked as `***` wherever the library prints it |
| `runOperation` | Adds a step from an `Operation<unit>`, with an optional label for `--explain`. Runs under the stage's working directory, environment, acceptable exit codes and output routing |
| `runHttpHealthCheck` | Adds a step that polls a URL until it answers or the stage is cancelled |
| `echo` | Adds a step that prints a message through the stage's output sink |
| `when'` | Runs the stage only when a `bool` holds, or only when a given `StageContext` succeeds. `when' (not quick) "--quick is set"` gives `--explain` the reason it prints against the skipped stage |
| `whenEnvVar` | Runs the stage only when an environment variable is set, or set to a given value; also takes an `EnvArg` |
| `whenBranch` / `whenBranches` | Runs the stage only on the named git branch. Reads `git branch --show-current` in the stage's working directory; a missing git evaluates false rather than throwing |
| `whenWindows` / `whenLinux` / `whenOSX` | Runs the stage only on that platform. Pass `false` to invert |
| `whenPlatform` | The same over an `OSPlatform` value |
| `workingDir` | The directory this stage's child processes start in. Takes a `string` or a `DirectoryInfo` |
| `envVars` | Environment variables for this stage's child processes. Applied to `ProcessStartInfo`, so the host process's own environment is untouched |
| `timeout` | Cancels the stage after the given duration |
| `timeoutForStep` | Cancels any one step of the stage after the given duration |
| `retry` | Runs the stage's steps again after a failing attempt, up to the given count. `timeout` remains the budget for the whole stage, retries included |
| `parallel'` | Runs the stage's steps concurrently. `true`/`0`/`-1` unbounded, `1`/`false` sequential, `n` throttled to exactly `n` in flight; also takes a `StageContext -> _` condition |
| `consumes` | Adds a step that reads a `DependencySpec<'D>` and runs an `Operation<unit>` over its value, and adds the required producers to this stage's prerequisites. See [Producers and dependencies](#producers-and-dependencies) |
| `onFailure` | Registers a handler that runs once, after this stage's own `retry` attempts are exhausted. See [Failure handlers](#failure-handlers) |
| `acceptExitCodes` | The exit codes that count as success. Replaces the default `[0]` |
| `failIfIgnored` | Fails the pipeline when this stage is inactive, instead of skipping it |
| `failIfNoActiveSubStage` | Fails the pipeline when none of this stage's sub-stages is active |
| `continueStepsOnFailure` | Runs the remaining steps after one fails |
| `continueStageOnFailure` | Runs the remaining stages after this one fails |
| `continueOnStepFailure` | Both of the above at once |
| `outputTo` | Sends this stage's step output to a `StageOutput` — `Console`, `Silent`, `Captured` or `Redirect` |
| `silentOutput` | Drops this stage's step output. A failure still reports its exit code |
| `captureOutput` | Holds this stage's step output back and lifts it into the error message when a step fails. Takes an optional `OutputCapture` to keep the lines either way |
| `redirectOutput` | Hands each line to `StdStream -> string -> unit` as it arrives, from both streams' reader threads |
| `noPrefixForStep` | Stops each step's output being prefixed with its stage and step index |
| `noStdRedirectForStep` | Stops redirecting the child's stdout/stderr — the mechanism every output operation above depends on — and overrides all of them |
| `shuffleExecuteSequence` | Randomises step order at each run |
| `verbosity` | How much of the pipeline's own log this stage prints. Takes `Verbosity.Quiet`, `Normal` or `Verbose` |
| `verbose` / `quiet` | `verbosity Verbose` and `verbosity Quiet` |

A stage nested inside another is one step of its parent. Stages nest to any depth. A block is a value that
`stage`, `pipeline` or `command` can yield.

### Running a command from inside a step

`run` and `runSensitive` cover the case where a command's exit code is the whole result. A step built with
`run (fun ctx -> ...)` reaches for one of these `Operation<'T>` functions when it needs the command's output as
a value:

| Function | What it does |
|---|---|
| `execute cmd` | Runs `cmd`, streaming its output through the stage's own output routing. Fails on an exit code the stage does not accept; carries no captured text |
| `executeCapture cmd` | Runs `cmd`, capturing stdout and stderr instead of streaming them. Fails on an unaccepted exit code the same way `execute` does, with the captured `CommandResult` attached to the failure as evidence |
| `attemptCapture cmd` | Runs `cmd`, capturing stdout and stderr, and always answers the `CommandResult` — an unaccepted exit code included. A process-start failure and a cancellation remain outcomes of their own, never a `CommandResult`; only a process that ran to completion produces one, and branching on its exit code is the caller's |

Captured stdout and stderr are the child's raw bytes, ahead of any prefix or display formatting — application
data that can hold secrets. Printing a successful capture is the caller's decision. Neither `executeCapture`
nor `attemptCapture` does it automatically.

## Pipeline operations

Available inside `pipeline "name" { }` and inside `Command.pipeline { }`, which takes the name and description
of the command that runs it.

| Operation | What it does |
|---|---|
| `description` | The pipeline's description. Discarded in `Command.pipeline { }`, which always takes the command's own name and description instead |
| `timeout` | Cancels the whole pipeline after the given duration |
| `timeoutForStage` | The default `timeout` of each stage |
| `timeoutForStep` | The default `timeoutForStep` of each stage |
| `workingDir` | The default working directory of every stage. Takes a `string` or a `DirectoryInfo` |
| `envVars` | Environment variables every stage inherits. Appends to the pipeline's map rather than replacing it |
| `acceptExitCodes` | The exit codes that count as success. Replaces the default `[0]` |
| `outputTo` | The default output sink of every stage |
| `silentOutput` | Drops every stage's step output |
| `captureOutput` | Holds every stage's step output back, lifting it into the error message on failure |
| `redirectOutput` | Hands every line of step output to `StdStream -> string -> unit` |
| `noPrefixForStep` | Stops step output being prefixed with the stage and step index |
| `noStdRedirectForStep` | Stops redirecting child stdout/stderr |
| `runBeforeEachStage` | A `StageContext -> unit` hook run before each stage. Replaces the previous hook |
| `runAfterEachStage` | A `StageContext -> unit` hook run after each stage. Replaces the previous hook |
| `post` | The stages that run after the main stages whether or not the pipeline succeeded — the teardown slot. Replaces any post stages already declared |
| `verbosity` | How much the pipeline prints. Takes `Verbosity.Quiet`, `Normal` or `Verbose` |
| `verbose` / `quiet` | `verbosity Verbose` and `verbosity Quiet` |
| `onFailure` | Registers a handler that runs once per failed run, after the handlers of every stage of that run. See [Failure handlers](#failure-handlers) |

`command`/`rootCommand` carries no `onFailure` default. Such a default would have to cover a failure of
`InputSpec.Read` or of CLI parsing, ahead of every pipeline it runs. Nothing observes that failure today.

## Producers and dependencies

A `Producer<'T>` is a typed, named unit of deferred work with its own CLI inputs and its own prerequisites.
Declaring one registers its identity and harvests those inputs. Nothing runs until a consumer schedules it.

| Function | What it does |
|---|---|
| `Producer.define name inputs dependencies execute` | Declares a producer: its own `InputSpec<'I>`, a `DependencySpec<'D>` of prerequisites, and the work computing `'T` from both |
| `Producer.stage` | Places a producer at this exact point of a pipeline or parent stage, rather than leaving its placement implicit |
| `Producer.emptyDefine name execute` | Declares a producer with no inputs or dependencies; identifies the operation as one that should only be run once. |
| `DependencySpec.empty` | A specification with no prerequisites |
| `DependencySpec.require producer` | A specification requiring one producer and reading its result |
| `DependencySpec.map fn spec` | The prerequisites of `spec`, its value read through `fn` |
| `DependencySpec.map2 fn first second` | The prerequisites and inputs of both specifications, unioned, their values read through `fn` |
| `DependencySpec.zip first second` | A specification requiring both producers and reading their results as a pair |
| `Stage.consuming name dependencies execute` | A stage whose one step is `execute` run over `dependencies`, with no CLI inputs of its own |
| `Stage.consumingWith name inputs dependencies execute` | The same, plus CLI inputs the stage itself declares |

A producer's handle carries an identity allocated when declared. Two declarations sharing a name and arguments
are distinct producers with distinct results. Depending on the same handle from more than one consumer runs it
once per invocation and shares that result. An unlisted producer required by a stage runs immediately before
that stage, after its own prerequisites. Listing it explicitly (`Producer.stage`, or yielding it into a
pipeline) fixes its position instead. A consumer running under a `parallel'` or `shuffleExecuteSequence` scope
reads a value published before that scope began. Placing a producer inside such a scope fails validation,
naming the producer and the scope.

A required producer that is skipped or fails leaves its consumers skipped, carrying a dependency reason. An
`Option`/`ValueOption` result models an intentional absence, distinct from a failed producer, and a consumer
can handle it directly. Retrying a consumer through `retry` reuses the successful results of producers outside
the retried scope. A producer owned by the retried scope gets a fresh result on each attempt, and a failed
attempt leaves no value for the next one to read.

## Failure handlers

`onFailure` registers a `FailureContext -> unit` handler on a `stage` or a `pipeline`. It runs once per failed
execution of that scope, after the scope exhausts its `retry` attempts, and inner handlers run before outer
ones — a stage's handler before the pipeline's. A recovered retry, or a cancelled stage, runs no handler. A
stage's own `timeout` and `timeoutForStep` count as failures of that stage. A cancellation from an ancestor's
token, the pipeline's, or the invocation's runs no handler.

The handler reads `FailureContext.Primary`/`.Secondary` for the causes recorded, and
`FailureContext.TryGetOutput producer` for a value the invocation has already published — answering `ValueNone`
for a producer that has not run. An exception out of a handler becomes one more cause of the same scope. The
original failure stays primary, and successful reporting preserves it. A failing handler triggers no second run
of itself.

Known limitations:

- A handler is synchronous and unbounded, with no cleanup operation or budget separate from its own body.
- `onFailure` has no equivalent on `command`/`rootCommand`. A failure of `InputSpec.Read` or of CLI parsing
  reaches no handler.
- A stage with no `timeoutForStep` runs its steps under the attempt's own cancellation source, not a budget of
  its own. A step still in flight when its scope unwinds keeps its sources undisposed, rather than racing a
  straggler that may still read them.
- `FailureCause.summarise`, used in `ScopeReports`, keeps only the first line of a multi-line capture. Later
  lines are lost from the report, not merely hidden from the rendering.
- `OperationFailedException`'s message is written by hand for each `FailureCause` case rather than through
  `FailureCause.describe`, so the two can drift.
- `whenStageSucceeds` (the body of `whenStage`) reads the policy-folded outcome of the condition stage: one
  carrying `continueStageOnFailure` reports itself as succeeded even where it failed.
- The gate placing an unlisted producer immediately before a `whenStage` consumer evaluates that consumer's
  condition stage a second time, beyond `whenStage`'s own evaluation.
- `PipelineContext.run`, called directly rather than through a command's own invocation, skips
  `DependencyPlan.validate`: an arrangement validation would reject — a producer inside a `parallel'` scope,
  say — runs instead of failing up front.

## Migrating work out of `InputSpec.Read`

`InputSpec<'T>.Read` is a projection, `ParseResult -> 'T`, called once per invocation to bind the CLI values a
stage declared. Effects belong in a step or a producer, not in `Read`: a `Read` that shells out or writes a
file runs on every path that resolves inputs, `--help` and `--explain` included, since resolution happens ahead
of the flag check.

Before, doing the work inside `Read`:

```fsharp
let publish =
    input {
        let! tag = Input.option<string> "--tag" |> Input.def "v0.0.0"
        // Runs on every resolution of this input, --help and --explain included.
        let manifest = fetchManifest tag
        return stage "publish" {
            run (cmd $"deploy --version {manifest.Version}")
        }
    }
```

After, the same CLI option feeding a producer, and the stage consuming its typed result:

```fsharp
let tag = Input.option<string> "--tag" |> Input.def "v0.0.0"

let manifest: Producer<Manifest> =
    Producer.define "manifest" (InputSpec.ofInput tag) DependencySpec.empty (fun tag () ->
        Operation.ofAsync (fetchManifestAsync tag))

let publish =
    pipeline "release" {
        stage "publish" {
            retry 2
            onFailure (fun context ->
                context.TryGetOutput manifest
                |> ValueOption.iter (fun manifest -> printfn $"publish failed for {manifest.Version}"))
            consumes (DependencySpec.require manifest) (fun manifest ->
                execute (cmd $"deploy --version {manifest.Version}"))
        }
    }
```

`Read` now binds only the option. `--help` and `--explain` resolve it without running `fetchManifest` or
`deploy`, since a producer runs only where its consumer is scheduled. The consumer's `retry` repeats the deploy
alone: `manifest` is required, not retried, so a failing deploy re-reads the same published value rather than
re-fetching it. Its `onFailure` reads that same value back out of the failure it is given. The CLI layer stays
applicative: `tag` is still an ordinary `ActionInput<string>`, readable without a `ParseResult`, exactly as
before.

## Command operations

Available inside `command "name" { }` and `rootCommand argv { }` / `rootCommandOfScript { }`, except for the
three marked as root-only.

| Operation | What it does |
|---|---|
| `description` | The command's description, shown in help |
| `alias` / `aliases` | Alternative names for the command. These accumulate |
| `hidden` | Keeps the command out of help output |
| `addCommand` / `addCommands` | Adds subcommands. Yielding a `Command` value does the same |
| `addInput` / `addInputs` | Registers an option or argument no pipeline asks for. Options a stage binds are registered already |
| `timeout` | Pipeline default: the whole run. Takes `int` seconds or a `TimeSpan` |
| `timeoutForStage` | Pipeline default: each stage. Takes `int` seconds or a `TimeSpan` |
| `timeoutForStep` | Pipeline default: each step. Takes `int` seconds or a `TimeSpan` |
| `workingDir` | Pipeline default: the directory commands run in |
| `envVars` | Pipeline default, per key: a pipeline that sets one of these keys itself keeps its own value and the rest still apply |
| `acceptExitCodes` | Pipeline default: the exit codes that count as success |
| `outputTo` / `silentOutput` / `captureOutput` / `redirectOutput` | Pipeline default: where step output goes |
| `noPrefixForStep` / `noStdRedirectForStep` | Pipeline default: prefixing and child stream redirection |
| `runBeforeEachStage` / `runAfterEachStage` | Pipeline default: the per-stage hooks |
| `post` | Pipeline default: the teardown stages |
| `verbosity` / `verbose` / `quiet` | Pipeline default: how much the pipeline prints |
| `name` | **Root only.** What the root command calls itself in help and usage. Defaults to the script's filename |
| `parserConfiguration` | **Root only.** A `System.CommandLine` `ParserConfiguration` |
| `invocationConfiguration` | **Root only.** A `System.CommandLine` `InvocationConfiguration` |

A command yields stages directly — `command "test" { Stages.restore; Stages.test }` — and consecutive stages
become one implicit pipeline carrying the command's name and description. `Command.pipeline { }` is that same
pipeline written out, for when it needs pipeline-level settings. `pipeline "name" { }` is for several pipelines
under one command, or a pipeline that needs its own name.

## Flags every command carries

| Flag | What it does |
|---|---|
| `--explain` | Prints the resolved stage tree and runs nothing. A grouping command lists its subcommands |
| `--json` | With `--schema`, selects compact JSON. With `--explain`, prints the tree as compact JSON, leaving `whenBranch` and `whenStage` conditions unevaluated. On a run, prints the run result — each stage's outcome, failures and timing — as one line of JSON after the run, in place of the timing table |
| `--report <path>` | Writes the run result as JSON to a file. Only on commands that run pipelines |
| `--schema` | Prints the command, its options (name, aliases, type, default, accepted values, description) and its subcommands as JSON, and runs nothing. Indented unless `--json` is true; `--schema --json false` explicitly requests indentation |

`--json` defaults to true in an agent environment, detected lazily from `AGENT`, `AI_AGENT` or vendor markers
following [is-ai-agent's environment rules](https://github.com/sdairs/is-ai-agent#detection-order).
Use `--json false` to select text explicitly, or `PARTAS_BUILD_DISABLE_AI=1` to disable automatic defaults.
The override leaves explicit `--json` available. Detection is fresh per invocation in a long-lived host.
Neither `--explain` nor `--schema` is enabled automatically, and consumer-declared options keep their defaults.

A command that declares one of these names itself keeps its own option. Every JSON document carries
`formatVersion`. Text `--explain` evaluates every condition, running a `whenStage` condition stage once; a
condition that throws is shown with its message. `Conditions.effectful description condition` marks a
condition of your own so the JSON form leaves it unevaluated too.

## Exit codes

| Code | `ExitCode` / `RunOutcome` | When |
|---|---|---|
| `0` | `Success` / `Succeeded` | Every pipeline succeeded, or the invocation printed help, a version, `--explain` or `--schema` |
| `1` | `Failure` / `Failed` | A stage failed, or the invocation raised an exception |
| `2` | `UsageError` / `UsageError` | The command line did not parse or validate, or the selected pipelines failed dependency validation, `--explain` included. No stage ran |
| `130` | `Cancelled` / `Cancelled` | The pipeline's own timeout expired, or the invocation's cancellation token fired |

A stage's own `timeout` is a failure of that stage (`1`), not a cancellation. Ctrl+C terminates the process by
signal: it writes no run result, `--json` line or `--report` file, and the exit status is the shell's (`130` on
Unix by convention).

## Invoking a command from code

`rootCommand argv { }` parses and runs as it is constructed, and answers the exit code. `Command.root { }` takes
the same operations and builds a `RootCommandDefinition` without parsing or running anything, so a host binds it
once and invokes it any number of times. [Hosting a build in a long-lived session](hosting.md) walks through it.

| Member | What it does |
|---|---|
| `Command.root { }` | Builds a `RootCommandDefinition`: every operation `rootCommand` takes, nothing run |
| `Command.invoke args root` | Parses `args` (without the script name), runs what they select, and answers a `RunResult` |
| `root.Invoke(args, ?output, ?error, ?cancellationToken)` | The same, with a `TextWriter` for everything the run prints (help, `--explain`, the pipeline's lines and console-sink child processes, as plain text), a writer for parse errors, and a token that cancels the run |
| `root.Command` | The underlying `System.CommandLine` `RootCommand` |

`Invoke` runs on a thread of its own. `Thread.Interrupt` on the calling thread cancels the run as the token
would — killing the process tree of any running step — then raises `ThreadInterruptedException` from `Invoke`.

`RunResult` is `{ ExitCode; Outcome: RunOutcome; Pipelines: PipelineRun list }`, where each `PipelineRun` is
`{ Name; Reports: ScopeReport list; Timings: StageTiming list }`, snapshots taken as the pipeline finished. Its
members flatten across pipelines: `Reports`, `Timings` (pre-order) and `Failures` (every `StepFailure`, tolerated
ones included). `RunResult.toJson indented result` is the document `--json` and `--report` write.

## Condition builders

`whenAll { }`, `whenAny { }` and `whenNot { }` take these. Each yields a single condition to a stage. An empty
`whenAll`/`whenNot` is always active. An empty `whenAny` never is.

| Operation | What it does |
|---|---|
| `when'` | A `bool`, or a `StageContext` that must succeed |
| `envVar` | An environment variable by name, by name and value, or as an `EnvArg` |
| `branch` / `branches` | The current git branch |
| `platformWindows` / `platformLinux` / `platformOSX` | The running platform. Pass `false` to invert |
| `platform` | The same over an `OSPlatform` value |

`whenEnv { }` describes one environment variable in place of a wall of overloads: `name`, `description`,
`value`, `acceptValues` and `optional`. `whenStage "name" { }` runs a stage for its result — everything `stage`
accepts is accepted, and the stage runs for real, side effects included.

`whenSome value build` and `whenOk value build` are functions rather than operations. Each returns a
`StageContext list`: the stage built from the bound value, or `[]`. The absent case is an empty list, not an
inactive stage requiring a name. `build` receives the value already unwrapped, inside the condition that guards it.

A condition decides whether a stage runs, not whether it is built: a stage body, both branches of an `if`
included, is built before any condition is evaluated. `key.Value` under `when' key.IsSome` throws while the
stage is built when `key` is `None`; `whenSome key (fun key -> ...)` builds the stage only for `Some`. An
exception raised while a command builds its stages fails the invocation (exit `1`) with a message naming the
command and the stages being built.

## `Input` combinators

Declaring functions:

| Function                                                  | What it makes |
|-----------------------------------------------------------|---|
| `Input.option<'T> "--name"`                               | An option bound as `'T` |
| `Input.optionMaybe<'T> "--name"`                          | An option bound as `'T option`, `None` when absent |
| `Input.argument<'T> "name"`                               | A positional argument bound as `'T` |
| `Input.argumentMaybe<'T> "name"`                          | A positional argument bound as `'T option` |
| `Input.context`                                           | Injects the `ActionContext` — the `ParseResult` and a cancellation token |
| `Input.inject value`                                      | Injects a value that is not parsed from the command line |
| `Input.ofOption` / `Input.ofArgument`                     | Lifts a raw `System.CommandLine` `Option<'T>` / `Argument<'T>` |

Shaping combinators, all `ActionInput<'T> -> ActionInput<'T>` and all pipeable:

| Function                                                            | What it does                                                                                                              |
|---------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|
| `Input.alias` / `Input.aliases`                                     | Adds alternative names. Options only                                                                                      |
| `Input.description` (`Input.desc` is obsolete)                      | The help text                                                                                                             |
| `Input.helpName`                                                    | The value placeholder in help — `<Debug\|Release>`                                                                        |
| `Input.defaultValue`, `Input.def`                                   | The value used when the token is absent                                                                                   |
| `Input.defaultValueFactory`                                         | The same, computed from the `ArgumentResult`                                                                              |
| `Input.arity`                                                       | How many values are accepted: `ExactlyOne`, `OneOrMore`, `Zero`, `ZeroOrMore`, `ZeroOrOne`, or `ArgumentArity (min, max)` |
| `Input.required`                                                    | Marks an option required                                                                                                  |
| `Input.recursive`                                                   | Applies the option to the command and, recursively, its subcommands                                                       |
| `Input.hidden`                                                      | Keeps it out of help output                                                                                               |
| `Input.sensitive`                                                   | Marks the symbol secret: each value read from it prints as `***` in every logged or explained command line, and `--schema` reports it `"sensitive": true` with a present default written as `"***"` |
| `Input.allowMultipleArgumentsPerToken`                              | Lets one identifier token carry several values                                                                            |
| `Input.acceptOnlyFromAmong`                                         | Restricts to a set of legal strings, ordinally                                                                            |
| `Input.addCompletion` / `Input.addCompletions`                      | Adds tab-completion suggestions without restricting what is accepted                                                      |
| `Input.mapFromAmong<'T> [ "key", value ]`                           | An option over a known set, each key bound to a typed value                                                               |
| `Input.mapFromAmongWith<'T> comparer`                               | `mapFromAmong` under an explicit `StringComparer`                                                                         |
| `Input.mapFromMany` / `mapFromManyWith`                             | The repeatable forms, binding `'T list`                                                                                   |
| `Input.acceptLegalFileNamesOnly` / `Input.acceptLegalFilePathsOnly` | Restricts to legal file names / paths                                                                                     |
| `Input.validate`                                                    | A `'T -> Result<unit, string>` check; `Error` becomes a CLI validation message                                            |
| `Input.validateFileExists` / `Input.validateDirectoryExists`        | The two common cases, over `FileInfo` / `DirectoryInfo`                                                                   |
| `Input.addValidator`                                                | A raw `SymbolResult -> unit` validator                                                                                    |
| `Input.customParser`                                                | An `ArgumentResult -> 'T` parser                                                                                          |
| `Input.tryParse`                                                    | An `ArgumentResult -> Result<'T, string>` parser; `Error` becomes a parse diagnostic instead of an exception              |
| `Input.editOption` / `Input.editArgument`                           | Reaches the underlying `Option<'T>` / `Argument<'T>` for anything not covered above                                       |

## `InputSpec<'T>`

`InputSpec<'T>` is public at `Partas.Build`. A stage factory parameterised by an option needs no
`open Partas.Build.Internal`:

```fsharp
let build (projects: InputSpec<string list>) = input {
    let! projects = projects
    and! config = Options.config
    ...
}
```

| Function | What it does |
|---|---|
| `InputSpec.ofInput` | Lifts an `ActionInput<'T>` into a spec |
| `InputSpec.ret` | A spec that reads nothing and returns a constant |
| `InputSpec.map` | Reshapes the value a spec reads |
| `InputSpec.map2` | Combines two specs, unioning their inputs |
| `InputSpec.sequence` | A list of specs into one spec of a list |
| `InputSpec.traverse` | `sequence` over the results of a mapping |
| `InputSpec.union` | Concatenates input lists, keeping the first occurrence of each |

The `input { let! … and! … return … }` CE is the usual way to build one; `inputs` is the same builder under
an obsolete second name (`src/Partas.Build/Builders/Inputs.fs` binds both). It is applicative: bind every source
in a single `let!`/`and!` group. A sequential second `let!` is a compile error (`FS0708`): the input set must
be readable before anything is parsed. An `input { }` nested inside another's `return` produces an
`InputSpec<InputSpec<_>>`, which nothing accepts — pass the *source* in as an `InputSpec` instead.

## `Cmd`

A `Cmd` keeps the executable and its arguments apart all the way to `ProcessStartInfo.ArgumentList`, so the
platform does the escaping.

| Function | What it does |
|---|---|
| `cmd $"dotnet build {project}"` | Each hole becomes exactly one argument, whatever it contains. `run $"..."` binds to the `string` overload and flattens the holes, so interpolate through `cmd` |
| `Cmd.ofString` | Splits a whole command line, honouring `"` and `'` |
| `Cmd.create exe args` | The executable exactly as given, plus an argument string split as `ofString` does |
| `Cmd.ofList exe args` | Both exactly as given |
| `Cmd.arg` / `Cmd.args` | Appends arguments exactly as given |
| `Cmd.argIf cond values` | Appends only when `cond` holds — one line instead of two whole command lines under an `if` |
| `Cmd.argWhenSome value render` | Appends the arguments rendered from a `Some`, and nothing from a `None` |
| `Cmd.secretArg` | Appends one argument whose value is masked wherever the command is printed |
| `Cmd.secretOption flag value` | Appends a visible flag and a masked value: `-k ***` |
| `Cmd.secretOptionWhenSome flag value` | The same when the value exists, appending nothing otherwise |
| `Cmd.secret` / `Cmd.sensitive` | Marks a string unprintable before it goes into a `cmd` hole |
| `Cmd.ofFormattable secret` | The interpolation reader behind `cmd` and `runSensitive` |
| `Cmd.toLogString` | How the command prints: secrets masked, whitespace-carrying arguments quoted |

## `Args`

The arguments a script was given, as distinct from the ones its host was given.

| Function | What it answers |
|---|---|
| `Args.script ()` | The running script's own arguments. `rootCommandOfScript { }` is `rootCommand (Args.script ()) { }` |
| `Args.scriptName ()` | The running script's filename, when it was launched as one |
| `Args.afterScript argv` | Everything after the `.fsx` in `argv`, or after `argv[0]` when there is none. A leading `--` is dropped |
| `Args.take argv` | Everything after the first `--` |
| `Args.nameOf argv` | The filename of the first `.fsx` in `argv` |

`dotnet fsi build.fsx -- test --quick` does not reach the process with its `--` intact: the `dotnet` driver
consumes one before `fsi` sees the command line. `Args.script` locates the script's own filename instead of
splitting on a separator.

## `Baked`

Ready-made declarations for the options every build CLI ends up wanting. They ship in their own package,
`Partas.Build.Baked`.

Each declaration is a `BuildOption<'T>` carrying both forms: `.option` is the flag, `.argument` the positional
equivalent. `BuildOption.map`, `.mapOpt` and `.mapArg` apply an `Input.*` combinator to both forms or to one.

| Value | What it declares |
|---|---|
| `Baked.NuGet.apiKey` | `nuget-key` (aliases `--nuget`, `-k`) as `string option`, defaulting to the `NUGET_API_KEY` environment variable |
| `Baked.Dotnet.config` | `configuration` (alias `-c`) as `string option`, over `release`/`r`/`debug`/`d` case-insensitively |
| `Baked.SemVer.bump` | `bump` as `Bump option`, over `major\|minor\|patch\|alpha\|beta\|rc\|preview\|<SEMVER>`, defaulting to `Patch` |
| `Baked.Common.isCI` | `--ci`, defaulting to true when any of the usual CI environment variables is set |
| `Baked.Common.quick` | `--quick` (alias `-q`): skips restores, installations, cleaning and formatting |
| `Baked.Common.skipTests` | `--skip-tests` |
| `Baked.Common.watch` | `--watch` |
| `Baked.Dotnet.configOrRelease` | `InputSpec<string>`: the `--configuration` value, `Release` when it is omitted |

`Baked.Stages` holds ready-made stages, each an `InputSpec<StageContext>` that `stage`, `pipeline` and `command`
yield directly and whose options reach the command's `--help`. Each reads Baked's own options; its `…With`
counterpart takes each of those options as an `InputSpec<_>` instead. A skip reports its reason to `--explain`.

| Function | What it does |
|---|---|
| `Stages.restore solution` | `dotnet tool restore`, then `dotnet restore`; skipped by `--quick` |
| `Stages.clean directories files` | Empties the directories and deletes the files the glob patterns select (`!` excludes), under the stage's working directory, following no links; skipped by `--quick` |
| `Stages.build projects` | One `dotnet build -c <configuration>` sub-stage per project, in parallel |
| `Stages.pack outDir projects` | One `dotnet pack --no-build --no-restore` sub-stage per project, in parallel |
| `Stages.expecto project arguments` | Runs a built Expecto suite through `dotnet run --no-build`; skipped by `--skip-tests`; under `--ci`, passes `--summary` and captures the output |
| `Stages.nugetPush packages` | `dotnet nuget push --skip-duplicate`, to nuget.org with `--nuget-key` (masked everywhere it prints) or to the source named `local` without one |
| `Stages.fantomas paths` | `dotnet fantomas` over the paths; skipped by `--quick` |
| `Stages.npmInstall directory` | `npm ci` under `--ci`, `npm install` otherwise; skipped by `--quick` |

| Function | What it does |
|---|---|
| `Baked.SemVer.Version.apply bump version` | Semantic version arithmetic over a `Bump` |
| `Baked.SemVer.Version.assembly version` | The assembly version that goes with a package version: its major, and nothing else |
| `Baked.SemVer.Version.IO.writeVersion` / `setVersion` | Rewrites `<Version>` and `<AssemblyVersion>` in a project file |
| `Baked.SemVer.Version.IO.bumpVersion projPath bump` | Applies a bump to a project file in place, answering the versions before and after |
| `Baked.SemVer.Stages.bumpArgument projects` | A `bump` stage taking the bump kind as a positional argument |
| `Baked.SemVer.Stages.bumpOption projects` | The same with the bump kind as `--bump` |

## Reference

- [Overview](index.md) — the layers, and a first pipeline.
- [Composing reusable blocks](composition.md) — blocks, nesting, and composition across files.
- [Hosting a build in a long-lived session](hosting.md) — `Command.invoke` from SageFs or any F# host.
- [Stage CE run overloads](computation-expression-operations.md).
- [API reference](https://shayanhabibi.github.io/Partas.Build/reference/) — full signatures and remarks.
