Skip to content

Gates

Gates are automated quality checks that must pass before a WU can be completed. They replace manual code review with consistent, automated enforcement.

Traditional review:

  • Human bottleneck (waiting for reviewers)
  • Inconsistent (different reviewers, different standards)
  • Slow feedback (review happens after code is written)

Gates:

  • Instant (run automatically)
  • Consistent (same checks every time)
  • Fast feedback (run locally before pushing)

Define your gate commands in workspace.yaml under software_delivery.gates:

# workspace.yaml
version: '2.0'

software_delivery:
  gates:
    execution:
      setup: 'pnpm install'
      format: 'pnpm format:check'
      lint: 'pnpm lint'
      typecheck: 'pnpm typecheck'
      test: 'pnpm test'

Projects with manual database deploy steps can add an explicit migration-state verifier:

software_delivery:
  gates:
    commands:
      migration_verify: 'pnpm db:preflight'

When migration_verify is configured, pnpm gates and pnpm wu:prep run it only when the working diff touches schema or migration paths such as db/schema/**, prisma/schema.prisma, supabase/schema.sql, or migration directories.

Use this for commands that check whether the target database is up to date. Do not use it to run migrations automatically.

This approach works with any language and toolchain.

wu:prep can enforce an extra proof step: if a WU changes code, it must also touch at least one automated test file in the same diff. This policy is configured under software_delivery.gates.tdd_diff_evidence.

software_delivery:
  gates:
    tdd_diff_evidence:
      mode: block
      applies_to_types:
        - feature
        - bug
      exempt_paths:
        - '.github/workflows/**'
        - '**/*.yml'
        - '**/*.yaml'

Defaults come from software_delivery.methodology.testing:

  • tdd sets software_delivery.gates.tdd_diff_evidence.mode: block
  • test-after sets software_delivery.gates.tdd_diff_evidence.mode: off
  • none sets software_delivery.gates.tdd_diff_evidence.mode: off
  • applies_to_types defaults to feature and bug
  • exempt_paths defaults to []
  • test_file_patterns defaults to TS/JS globs (**/*.test.{ts,tsx,js,jsx,mjs}, **/*.spec.{ts,tsx,js,jsx,mjs}, **/__tests__/**, **/*.test-utils.*, **/*.mock.*)
  • code_file_extensions defaults to TS/JS extensions (.ts, .tsx, .js, .jsx, .cjs, .mts, .cts)

mode supports block, warn, and off. wu:prep only blocks when the mode is block; teams that want the policy disabled can set warn or off and still document their intent in config.

test_file_patterns and code_file_extensions make the gate language-agnostic. Override either field to teach the gate which files count as tests vs production code in your workspace. Overrides replace the defaults — supply only your toolchain’s patterns.

C# (xUnit / NUnit / MSTest):

software_delivery:
  gates:
    tdd_diff_evidence:
      test_file_patterns:
        - '**/*Tests.cs'
        - '**/*.Tests.cs'
        - '**/Test*.cs'
      code_file_extensions:
        - '.cs'

Python (pytest / unittest):

software_delivery:
  gates:
    tdd_diff_evidence:
      test_file_patterns:
        - '**/test_*.py'
        - '**/*_test.py'
        - '**/tests/**/*.py'
      code_file_extensions:
        - '.py'

Go:

software_delivery:
  gates:
    tdd_diff_evidence:
      test_file_patterns:
        - '**/*_test.go'
      code_file_extensions:
        - '.go'

Use this gate when you want changed-test evidence for specific WU types or runtime paths. It is not a requirement to force every team into test-first development.

For one-off exceptions, document the reason in the WU notes with:

tdd-exception: <reason>

The Software Delivery pack also exposes a native delivery_review gate for completion review. This capability is public and vendor-agnostic:

  • It lives under software_delivery.gates.delivery_review
  • enabled: true registers it for every agent/client runtime
  • auto_run: true makes wu:prep run it for applicable WU types
  • It runs through native gate execution and wu:prep, not through a vendor-specific skill path
  • It does not depend on lumenflow-cloud or any hosted control plane
software_delivery:
  gates:
    delivery_review:
      enabled: true
      auto_run: true
      block_partial: true
      verifier_command: pnpm qa:evidence
      skip_types:
        - documentation
        - process

pnpm gates runs delivery_review whenever the global gate is enabled. pnpm wu:prep auto-runs it when both enabled: true and auto_run: true are set, unless the current WU type matches skip_types. The same public contract is catalogued in the Gates Reference.

For source-code delivery changes, delivery_review requires automated test evidence or meaningful manual verification evidence. A non-empty tests.manual entry is not enough by itself: placeholders and negative values such as todo, n/a, screenshot: n/a, and not run are treated as missing evidence and fail the gate. Use concrete manual entries that name the surface, action, observed result, and artifact path when visual or manual QA is the right evidence.

Set block_partial: true when the repo wants delivery-review uncertainty to block rather than warn. Set verifier_command when native evidence-shape checks should be followed by project-owned truth checks. The command runs from the repository root, receives LUMENFLOW_DELIVERY_REVIEW_WU_ID=<WU-ID>, and blocks the gate when it exits non-zero.

Client-specific config can still exist for adapter UX, hooks, or prompt surfacing, but it is not the enforcement switch:

software_delivery:
  agents:
    defaultClient: codex-cli
    clients:
      claude-code:
        features:
          delivery_review:
            enabled: true
            auto_run: true

For one release, legacy client-scoped features.delivery_review.auto_run: true is still honored when the global gate is enabled and global auto_run is omitted. LumenFlow emits a migration warning pointing to software_delivery.gates.delivery_review.auto_run. Client-scoped features.delivery_review.enabled: false does not disable the core gate; disable or skip the gate through the normal global gate config or auditable gate-skip mechanism.

delivery_review produces a stable JSON artifact at .lumenflow/artifacts/delivery-review/<WU-ID>.json. Hosts and products can consume the result without assuming a specific vendor runtime.

type DeliveryReviewVerdict = 'PASS' | 'FAIL' | 'PARTIAL';

interface DeliveryReviewResult {
  wuId: string;
  verdict: DeliveryReviewVerdict;
  summary: string;
  findings: Array<{
    severity: 'critical' | 'high' | 'medium' | 'low';
    title: string;
    detail: string;
    acceptanceCriteriaRefs?: string[];
    fileRefs?: string[];
  }>;
  acceptanceCriteria: Array<{
    criterion: string;
    status: 'satisfied' | 'unclear' | 'not_satisfied';
    evidence?: string[];
  }>;
  metadata: {
    runtimeClient?: string;
    startedAt: string;
    completedAt: string;
  };
}

Verdict behavior:

  • PASS means the review found sufficient delivery evidence
  • PARTIAL means the review completed with uncertainty or lower-severity findings; it warns by default and blocks when software_delivery.gates.delivery_review.block_partial is true
  • FAIL means the review found blocking gaps or risks and gates fail

The native review inspects the current WU spec, changed files, acceptance criteria, and delivery risks. It is intentionally separate from wu:verify, which keeps its existing lifecycle meaning.

Define pattern-triggered commands alongside your standard gates:

software_delivery:
  gates:
    execution:
      format: 'pnpm format:check'
      lint: 'pnpm lint'
      typecheck: 'pnpm typecheck'
      test: 'pnpm test'

    conditional_commands:
      - trigger_patterns:
          - 'supabase/migrations/**'
          - 'supabase/schema.sql'
        command: 'pnpm db:verify'
        severity: error
        guidance: 'Apply pending migrations locally before verifying database state.'
        guidance_ref: 'docs/db-verification-guide.md'

      - trigger_patterns:
          - 'prisma/migrations/**'
          - 'prisma/schema.prisma'
        command: 'npm run prisma:validate'
        severity: warn

| Field | Type | Required | Description | | ------------------ | ---------- | -------- | -------------------------------------------------------- | | trigger_patterns | string[] | Yes | Glob patterns matched against changed files | | command | string | Yes | Shell command to execute when patterns match | | severity | string | No | error (default, blocks gates), warn, or off (skip) | | guidance | string | No | Actionable text shown when the command fails | | guidance_ref | string | No | File path whose content is appended to guidance |

How it works:

  1. When pnpm gates or wu:prep runs, changed files are compared against each command’s trigger_patterns using glob matching
  2. Only commands with matching patterns execute — unmatched commands are silently skipped
  3. If a matching command fails with severity error, gates fail. With severity warn, a warning is logged but gates continue

Registering via the CLI (Constraint-9 compatible):

workspace.yaml must not be edited by hand. Two sanctioned paths exist:

  1. gate:conditional (recommended, per-rule) — mirrors gate:co-change:

    pnpm gate:conditional --add --name db-verify \
      --trigger "supabase/migrations/**" \
      --trigger "supabase/schema.sql" \
      --command "pnpm db:verify" \
      --severity error \
      --guidance "Apply pending migrations locally before verifying database state."
    
    pnpm gate:conditional --list          # human-readable
    pnpm gate:conditional --list --json   # machine-readable
    
    pnpm gate:conditional --edit --name db-verify --severity warn
    pnpm gate:conditional --remove --name db-verify

    The name field is how --remove/--edit address a specific entry. It is optional in the underlying schema (existing unnamed entries continue to work) but required for CLI-managed entries.

  2. config:set --json-value (escape hatch) — writes the whole array verbatim when you need a shape the flags above don’t cover:

    pnpm config:set --key software_delivery.gates.conditional_commands \
      --json-value '[{"name":"db-verify","trigger_patterns":["supabase/migrations/**"],"command":"pnpm db:verify","severity":"error"}]'

Both paths validate against ConditionalCommandConfigSchema and commit atomically via micro-worktree.

For common languages, use a preset to get sensible defaults:

software_delivery:
  gates:
    execution:
      preset: 'python'
      # Override specific commands
      lint: 'mypy . && ruff check .'

Available presets: node, python, go, rust, dotnet, java, ruby, php

Path-scoped tests.unit execution is preset-aware. When the active preset supports scoped execution, wu:prep can use the current WU’s tests.unit entries to narrow the test gate. When a preset does not support path-scoped execution, such as dotnet, LumenFlow falls back to the configured default test command for that preset instead of attempting a JavaScript-specific runner.

The immutable safety-critical-test gate owns the shared test plan. It runs declared tests.unit paths when they can be scoped safely; otherwise it runs the configured test_incremental command. The normal test gate reuses that exact result, so the same test process does not execute twice during one wu:prep.

The planner falls back to test_full when broader coverage is required:

  • main-snapshot comparison probes;
  • --full-tests or --full-coverage;
  • test-runner configuration changes;
  • unavailable change detection or untracked code; and
  • a missing, blank, or full-equivalent test_incremental command.

These fallbacks also execute once across the safety and normal test gates. Full CI remains the final whole-repository authority.

| Preset | Format | Lint | Typecheck | Test | | -------- | ------------------------ | --------------- | -------------- | ------------- | | node | prettier --check . | eslint . | tsc --noEmit | npm test | | python | ruff format --check . | ruff check . | mypy . | pytest | | go | gofmt -l . | golangci-lint | go vet ./... | go test | | rust | cargo fmt --check | cargo clippy | cargo check | cargo test | | dotnet | dotnet format --verify | dotnet build | - | dotnet test | | java | spotless:check | checkstyle | mvn compile | mvn test | | ruby | rubocop | rubocop | - | rspec | | php | php-cs-fixer | phpstan | - | phpunit |

Before gate context, telemetry, or any gate command starts, LumenFlow inspects the active checkout’s dependency roots and @lumenflow workspace-package links. Each workspace dependency must resolve inside the active checkout. Package managers may materialize shared content into a checkout-local virtual store via hardlinks, reflinks, or copies, but a direct workspace link to an external store is never accepted. Store-looking substrings do not establish trust. Every intermediate scope/package component is realpath-checked, including directory junctions. Missing/non-directory dependency roots, missing declared workspace packages, and paths into main or another worktree all fail closed.

The diagnostic includes both the exact contaminated link and its resolved target:

[gates] Dependency isolation preflight failed; refusing to execute gates:
  - /repo/worktrees/wu-a/packages/app/node_modules/@lumenflow/core -> /repo/worktrees/wu-b/packages/@lumenflow/core (workspace dependency resolves outside the active checkout)

Remove only the listed link, run the configured frozen install inside the active worktree, and then rerun gates. Do not relink to main and do not skip the check: no gate result is trustworthy when module resolution can read another branch.

A worktree gates invocation also requires its own CLI dist. It never falls back to main’s CLI dist, so a --skip-setup checkout cannot bypass this preflight by bootstrapping the gate runner from a different checkout.

Main-snapshot comparison probes follow the same boundary. Each temporary probe performs its own frozen install and blocks classification if installation or isolation verification fails.

# Run all gates
pnpm gates

# Output
> Format check... pass
> Lint check... pass
> Type check... pass
> Test suite... pass
> All gates passed!

If any gate fails, wu:prep fails and you fix issues in the worktree before completion.

For migration verification failures, the expected fix is:

  1. Apply the pending migrations using your project’s normal process
  2. Re-run the configured verification command manually if needed
  3. Re-run pnpm wu:prep --id WU-XXX

| Flag | Description | | -------------- | --------------------------------------------------------------------------- | | --docs-only | Run only docs-related gates (skip format/lint/typecheck/test) | | --full-tests | Force one full test_full execution instead of scoped or incremental tests | | --full-lint | Run full lint pass instead of scoped lint |

Commands can be strings or objects with options:

software_delivery:
  gates:
    execution:
      format: 'dotnet format --verify-no-changes'
      test:
        command: 'dotnet test --no-restore'
        timeout: 300000 # 5 minutes
        continueOnError: false
software_delivery:
  gates:
    execution:
      preset: 'node'
      # Or custom:
      setup: 'pnpm install --frozen-lockfile'
      format: 'pnpm prettier --check .'
      lint: 'pnpm eslint . --max-warnings 0'
      typecheck: 'pnpm tsc --noEmit'
      test: 'pnpm vitest run'
software_delivery:
  gates:
    execution:
      preset: 'python'
      # Or custom:
      setup: 'pip install -e ".[dev]"'
      format: 'ruff format --check .'
      lint: 'ruff check . && mypy .'
      test: 'pytest -v'
software_delivery:
  gates:
    execution:
      preset: 'dotnet'
      # Or custom:
      setup: 'dotnet restore'
      format: 'dotnet format --verify-no-changes'
      lint: 'dotnet build --no-restore -warnaserror'
      test: 'dotnet test --no-restore'
software_delivery:
  gates:
    execution:
      preset: 'go'
      # Or custom:
      format: 'test -z "$(gofmt -l .)"'
      lint: 'golangci-lint run'
      typecheck: 'go vet ./...'
      test: 'go test -v ./...'
software_delivery:
  gates:
    execution:
      preset: 'rust'
      format: 'cargo fmt --check'
      lint: 'cargo clippy -- -D warnings'
      typecheck: 'cargo check'
      test: 'cargo test'
software_delivery:
  gates:
    execution:
      preset: 'java'
      # Or custom (Maven):
      format: 'mvn spotless:check'
      lint: 'mvn checkstyle:check'
      typecheck: 'mvn compile -DskipTests'
      test: 'mvn test'
      # Or Gradle:
      # format: './gradlew spotlessCheck'
      # lint: './gradlew checkstyleMain'
      # typecheck: './gradlew compileJava'
      # test: './gradlew test'
software_delivery:
  gates:
    execution:
      preset: 'ruby'
      # Or custom:
      setup: 'bundle install'
      format: 'bundle exec rubocop --format simple --fail-level W'
      lint: 'bundle exec rubocop'
      test: 'bundle exec rspec'
software_delivery:
  gates:
    execution:
      preset: 'php'
      # Or custom:
      setup: 'composer install'
      format: 'vendor/bin/php-cs-fixer fix --dry-run --diff'
      lint: 'vendor/bin/phpstan analyse'
      test: 'vendor/bin/phpunit'

Each gate maps to a policy rule in the Software Delivery Pack:

| Gate | Policy ID | Trigger | | ------------ | ---------------------------------- | --------------- | | Format check | software-delivery.gate.format | on_completion | | Lint | software-delivery.gate.lint | on_completion | | Type check | software-delivery.gate.typecheck | on_completion | | Test | software-delivery.gate.test | on_completion |

When wu:prep or wu:done runs, the kernel evaluates these policies. A deny from any gate makes the completion decision final — the deny-wins invariant applies. The result is recorded in the evidence store for audit.

When you wu:claim:

  • Worktree is created
  • Gates status is “pending”

Run pnpm gates frequently:

# Quick feedback loop
pnpm gates
# Fix issues
pnpm gates
# All green? Continue

When you wu:prep:

  1. Gates run automatically in the worktree
  2. If any fail, the WU stays in_progress until you fix and rerun wu:prep

When you wu:done:

  1. The WU merges to main
  2. The stamp is created
  3. The worktree is cleaned up
pnpm wu:prep --id WU-042

> Running gates...
> Format check... FAILED
>   src/utils/validation.ts - needs formatting
>
> Fix the issues above before completing.

Fix and retry:

pnpm prettier --write src/utils/validation.ts
pnpm wu:prep --id WU-042
# Gates pass, then complete from main:
cd /path/to/main && pnpm wu:done --id WU-042

There is no bare pnpm gates --skip-<gate> flag. Skip a specific, named gate through wu:done instead — both --reason and --fix-wu are required, and only gates marked skippable: true can be named this way:

pnpm wu:done --id WU-042 \
  --skip-gate lane-health \
  --reason "Pre-existing test failure in legacy module" \
  --fix-wu WU-150

See Constraints — Gates and Named Gate Skips for the gates that are immutable and can never be skipped this way.

Or in configuration:

software_delivery:
  gates:
    execution:
      format: 'pnpm format:check'
      lint: 'pnpm lint'
      # No typecheck or test for this project

Use the LumenFlow Gates GitHub Action:

# .github/workflows/gates.yml
name: Gates
on: [pull_request]
jobs:
  gates:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hellmai/lumenflow/actions/lumenflow-gates@v4
        with:
          token: ${{ secrets.LUMENFLOW_TOKEN }}

The action reads your software_delivery.gates.execution config automatically. See GitHub Action docs for details.

If no software_delivery.gates.execution config is present, LumenFlow falls back to auto-detecting your project type based on files present and uses preset defaults.