Using scopes

Declare scopes, pin a blueprint to one, and gate shell steps on the active scope.

A scope is a string label attached to a blueprint that tells contractor which slice of the repo the change belongs to. In a monorepo, scopes typically name the affected package — cli, web, protocol. They're optional, but once declared they unlock two useful behaviors: {{scope}} substitution in pipeline shell steps, and a when: scope guard that lets the same pipeline serve scoped and unscoped blueprints.

This guide walks through declaring a scope set, pinning a blueprint to one, and using both substitution and the guard in a pipeline.

1. Declare the scope whitelist

List the valid scope values under scopes: in contractor/config.yaml:

Once scopes is declared, the validation rules kick in:

  • contractor blueprint new --scope <value> rejects any value not in the list with an error that prints the declared set.
  • The interactive --scope prompt offers a select instead of free text.
  • contractor blueprint list --scope <name> filters the listing; --scope unscoped shows blueprints with no scope.

If you don't declare scopes, the --scope flag accepts any string. That's fine for a small repo where you don't want the formality, but most teams want the validation as soon as the second scope shows up.

2. Pin a blueprint to a scope

Pass --scope when creating the blueprint:

This writes scope: cli into contractor/blueprints/add-resume-support/.contractor.yaml and tells the propose-phase agents (and the implement agent's preamble) to focus changes within packages/cli/. The blueprint can be re-scoped later by editing the YAML, but typically the scope is set at creation time and stays put.

A blueprint with no --scope and no scope in its .contractor.yaml is unscoped. That's a valid state — it just means {{scope}} substitutions and when: scope guards behave as documented below.

3. Use {{scope}} in shell steps

Pipeline shell steps support {{scope}} substitution. The placeholder is replaced with the blueprint's scope value before the command is spawned:

For a blueprint with scope: cli, the shell step expands to pnpm --filter @myorg/cli test. Substitution happens at spawn time, not at parse time, so the same pipeline definition serves every scope.

{{scope}} is only supported in kind: shell steps. It is not substituted in agent step prompts, slash command names, or other fields. Adding new template variables is a separate proposal — see Pipelines for the current step reference.

4. Guard scoped-only steps with when: scope

A kind: shell step that depends on {{scope}} should be guarded with when: scope:

The guard says: "skip this step when the active blueprint has no scope." Skipped steps are recorded as skipped in the pipeline run's child rows — not failed, not silently dropped. The pipeline continues as if the step weren't there.

Without the guard, an unscoped blueprint would either fail loudly (an unguarded {{scope}} reference with no scope value is rejected at spawn) or run with an empty substitution (which contractor explicitly does not allow — there's no pnpm --filter @myorg/ test). The guard is the supported way to make the same pipeline work for both scoped and unscoped blueprints.

5. Override the scope at run time

contractor run --scope <name> overrides the blueprint's pinned scope for a single invocation:

This is useful for experiments — running an unscoped blueprint as if it were scoped to protocol to see how the scoped shell step behaves. The override is in-memory; it does not rewrite .contractor.yaml. For a permanent change, edit the YAML.

If --scope names a value not in the declared whitelist (when one exists), the run is rejected with the same error blueprint new --scope produces.

6. Verify with contractor doctor

Doctor's "Schema graph" and project config sections call out scope-related issues:

  • A pipeline shell step with {{scope}} but no when: scope guard, in a repo whose blueprints can be unscoped.
  • A --scope value that doesn't match the declared set.
  • Stray {{scope}} references in non-shell steps (which are not substituted, so the literal string ends up in the prompt).

If contractor run --scope cli rejects with Unknown scope, check that cli is listed under scopes: in contractor/config.yaml (not in a blueprint's .contractor.yaml — the whitelist is project-level).

A complete example

The shell test step runs only for scoped blueprints. Unscoped blueprints skip it and proceed straight to review. If you want a fallback for unscoped runs, a separate pipeline (no when: scope guard, full-suite command) is the simplest answer today: select it with --pipeline for unscoped blueprints. The only guard literal when: accepts is "scope"; richer predicates like negation or boolean composition are not supported.

See also