Workflow overlays

Workflow overlays let a project extend or override an installed workflow without editing the installed workflow.yml. This keeps local customizations safe across specify bundle update or specify workflow add upgrades.

When specify workflow run <workflow-id> loads a workflow, the engine composes the base workflow with all enabled overlays for that workflow id. The result is validated like any other workflow definition.

How Overlays Work

An overlay is a YAML file that declares a set of edit operations against the step list of a base workflow. Overlays use lower-wins precedence: higher priority numbers are applied first and lower numbers last. Equal-priority overlays are applied alphabetically by ID, with the last ID winning conflicts.

Project overlay files live at:

Location Purpose
.specify/workflows/overlays/<id>/*.yml Project-local customizations

Overlay File Format

The recommended edit format uses the operation name as the key and the anchor step id as the value:

id: "my-overlay"
extends: "speckit"
priority: 10
enabled: true
edits:
  - insert_after: implement
    step:
      id: run-lint
      type: shell
      run: "ruff check src/"

  - replace: review-spec
    step:
      id: review-spec
      type: gate
      message: "Review the generated spec (overlay override)."
      options: [approve, reject]
      on_reject: abort

The explicit form is also supported:

edits:
  - operation: insert_after
    anchor: implement
    step:
      id: run-lint
      type: shell
      run: "ruff check src/"

Fields

Field Required Description
id yes Identifier for this overlay. Used in specify workflow overlay * commands. Must be lowercase letters, digits, and hyphens only; no dots, underscores, path separators, or overlays.
extends yes The workflow id this overlay applies to. Uses the same safe-id format as id; overlays, runs, and steps are reserved.
priority no Integer; defaults to 10. Lower values have higher precedence and win conflicts. Missing or invalid values fall back to 10.
enabled no Boolean. Defaults to true. Disabled overlays are ignored.
edits yes Non-empty list of edit operations.

Edit Operations

Operation step required Effect
insert_after yes Insert step immediately after the anchor step.
insert_before yes Insert step immediately before the anchor step.
replace yes Replace the anchor step with step.
remove no Remove the anchor step from the list.

The anchor is the id of a step in the base workflow. Anchors are resolved recursively inside then, else, steps, cases.*, and default blocks, so nested base steps can also be targeted. Fan-out templates (step inside a fan-out step) are not valid anchors.

Step ids must not contain : — that character is reserved for engine-generated nested ids.

Manage overlays

Use the overlay CLI commands to add, list, prioritize, enable, disable, and remove overlays, or inspect composition with specify workflow resolve <workflow-id>.

Example: Adding Automated Linting after Implementation

Given the built-in speckit workflow, create project-overlay.yml:

id: "add-lint"
extends: "speckit"
priority: 10
edits:
  - insert_after: implement
    step:
      id: run-lint
      type: shell
      run: "ruff check src/"

Install it:

specify workflow overlay add project-overlay.yml --priority 10

Run the workflow:

specify workflow run speckit -i spec="Build a kanban board"

The composed workflow will now run the full SDD cycle and execute ruff check src/ automatically after the implement step.

Example: Replacing a Gate

id: "skip-plan-review"
extends: "speckit"
priority: 5
edits:
  - replace: review-plan
    step:
      id: review-plan
      type: command
      command: speckit.plan
      input:
        args: "{{ inputs.spec }}"

Lower priority values have higher precedence. This overlay's priority: 5 wins conflicts with a priority-10 overlay. It replaces the review-plan gate with another plan command, so the existing plan command still runs too.

Workflow slots (upstream extension points)

Workflow authors can declare a named, no-op workflow slot with type: slot:

- id: post-implement
  type: slot
  name: "Post-implementation checks"

The step id is the unique overlay anchor; name is a required non-blank, human-readable label only. An unfilled slot completes as a skipped step with output: {slot: <name>}, so subsequent steps continue normally.

Fill a slot with a schema-valid overlay replace edit anchored on the step id, not its name:

id: fill-post-implement
extends: my-workflow
edits:
  - replace: post-implement
    step:
      id: post-implement
      type: shell
      run: "echo Run project-specific checks"

Reuse the slot's id when later expressions or fan-in.wait_for refer to it. The replacement must also preserve every output key those later steps consume: an unfilled workflow slot supplies only steps.<id>.output.slot. Slot steps are not supported inside fan-out.step templates because runtime-multiplied templates cannot be overlay anchors.

Interaction with Bundles and Updates

specify workflow add <local-directory> installs the complete local workflow package into .specify/workflows/<id>/. Archive installs preserve the same package contents.

When an installed workflow is refreshed or reinstalled, project overlays in .specify/workflows/overlays/<id>/ are preserved because they live outside the installed workflow directory.

Limitations

  • Overlays operate on the step list only. They cannot change workflow metadata, inputs, outputs, requires, or workflow-level defaults. To change an expression in a step, replace that step.
  • Fan-out templates cannot be used as anchors.
  • An overlay that targets a step id that does not exist in the base workflow will raise a validation error when the workflow is resolved.
  • Overlays cannot target steps added by other overlays.
  • Overlays cannot add new inputs or change the input schema of the base workflow.

See also