Skip to content

How Phases and Scopes Work

Leonard Ramminger edited this page Aug 9, 2026 · 1 revision

How Phases and Scopes Work

What phase and scope are

A phase is a string label for a pipeline stage. A scope is a string label for a variant within that stage.

Beez treats both as opaque text. There is no built-in list like configure or test. You define meaning in your build.lua.

phase "build" + scope "default"  ->  selects steps tagged build:default
phase "build" + scope "debug"    ->  selects steps tagged build:debug

Declaring phase and scope

Both fields are required on every step():

step({
    name = "compile",
    phase = "build",
    scope = "default",
    run = "...",
})

If either is missing, build.lua fails to load.

How Beez matches steps

When Beez runs a phase+scope pair (P, S), it collects every registered step where:

step.phase == P  AND  step.scope == S

No other fields are considered for selection. In particular:

  • Step name is not part of the selector
  • input / output / mutate globs do not define scope
  • Tasks and workflows do not auto-infer scope from file paths

Multiple steps per pair

Many steps can share the same phase and scope. They all run when that pair is invoked.

step({ name = "compile", phase = "build", scope = "default", run = "..." })
step({ name = "link",    phase = "build", scope = "default", run = "..." })

Both run for build:default. See Parallel Execution and Dependencies for ordering.

Step names are global

Step name must be unique in the registry. Phase and scope do not namespace names.

-- Valid: different names, same phase+scope
step({ name = "compile:release", phase = "build", scope = "default", ... })
step({ name = "link:release",    phase = "build", scope = "default", ... })

-- Invalid: duplicate name (second registration replaces first)
step({ name = "compile", phase = "build", scope = "default", ... })
step({ name = "compile", phase = "test",  scope = "unit",    ... })  -- overwrites

Use distinct names like build:compile and test:compile if that helps clarity. order() references name, not phase:scope.

What happens when a pair runs

For one invocation of (phase, scope):

  1. Collect all matching steps
  2. Order into levels using order() hints and mutate overlap rules
  3. Execute each level (parallel within a level unless -j 1)
  4. Flush buffered cache writes for this phase+scope (when cache_write_strategy = "phase")

If no steps match, Beez succeeds immediately with nothing to do.

Scopes run one after another

When multiple scopes are requested (for example beez -p test:unit,integration), Beez runs each scope completely before starting the next. Scopes are not parallel with each other unless you use a workflow parallel block.

Workflow vs single pair

Invocation Scope behavior
beez -p build All scopes for build, alphabetically sorted, sequential
beez -p build:debug,release debug then release, in listed order
Workflow sequential step One pair per workflow step
Workflow parallel block Multiple pairs at once

Phase+scope is not a file domain

Scope does not mean "which directories this step owns."

Concern Belongs on
Which files a step reads/writes input, output, mutate on the step
Which steps run together phase and scope
Run order inside a group order(), mutate inference

Two steps in qa:default may touch completely different paths.

Discovery from steps only

Phases and scopes exist because steps declare them. Beez does not register empty phases.

beez --list phases    # phases derived from steps
beez --list steps     # shows Name, Phase, Scope columns

A workflow can reference a phase+scope pair that has no steps yet. That workflow step becomes a no-op until you add matching steps.

Cache and logging

Step cache index files include phase and scope in the filename:

.cache/index/<name>__<phase>__<scope>.index

Success cache misses files use the same pattern under .cache/success/misses/.

Run logs may show segments like build:default when executing a phase+scope block.

Bypassing phase+scope selection

Action Behavior
beez -s compile Runs step compile regardless of phase/scope
beez my-task with { name = "compile" } Runs that step by name
Shell task string No steps involved

Workflows and -p are the main ways to run by phase+scope.

Mental model

build.lua
  steps (each has phase + scope + name)
       |
       v
  registry: group by (phase, scope)
       |
       +-- workflow / -p  -> pick pairs -> run matching steps
       +-- -s / task      -> pick by step name directly

Next steps

Clone this wiki locally