Verification Pipeline

Every changeset submitted by an agent passes through a verification pipeline before it can be merged. This is the quality gate that prevents broken code from reaching your repository.


How It Works

Loading diagram...

When an agent calls VERIFY, dkod:

  1. Copies the full repository into an isolated temp directory
  2. Overlays the changeset's modified files on top
  3. Resolves which pipeline to run (file, database, or auto-detect)
  4. Executes each stage and streams results back to the agent
  5. Updates the changeset status to approved or rejected

Pipeline Resolution Order

dkod resolves the verification pipeline in priority order:

  1. .dkod/pipeline.yaml in your repository (highest priority)
  2. Database pipeline configured via the API
  3. Auto-detected from project files (lowest priority)

Auto-Detection (Zero Configuration)

Most projects don't need any pipeline configuration. dkod automatically detects your project type from its files and runs the appropriate build and test commands. This works out of the box for all 16 supported languages:

Project FileLanguageDefault Steps
Cargo.tomlRustcargo check, cargo test
package.json + bun.lock / bun.lockbBunbun test
package.jsonNode.jsnpm test
pyproject.toml or requirements.txtPythonpytest
go.modGogo build ./..., go test ./...
pom.xml or build.gradleJavamvn test or gradle test
build.gradle.ktsKotlingradle test
*.csproj or *.slnC#dotnet build, dotnet test
GemfileRubybundle exec rake test
composer.jsonPHPcomposer test
Package.swiftSwiftswift build, swift test
build.sbtScalasbt test
*.cabal or stack.yamlHaskellcabal test or stack test
CMakeLists.txt or MakefileC / C++make test
Project.tomlJuliajulia --project test/runtests.jl
*.shBashshellcheck *.sh, bash -n *.sh
NoneAuto-approve with warning

Notes:

  • Kotlin projects using the Groovy DSL (build.gradle instead of build.gradle.kts) are detected as Java. Both run gradle test identically, so behavior is the same.
  • CMake-based C/C++ projects need a custom .dkod/pipeline.yaml with two separate steps: one for cmake -B build (configure) and one for cmake --build build (build), before make test. Both cmake -B and cmake --build must be listed in allowed_commands. Shell operators like && are blocked and cannot combine them into a single run: value.

Custom Pipeline Configuration (Optional)

For most projects, auto-detection is all you need. Custom pipelines are useful when you want to add extra steps (linting, AI review, manual approval gates) or restrict which commands are allowed. Create .dkod/pipeline.yaml in your repository root to override the defaults.

Schema

pipeline:
  name: my-project          # Pipeline name (for logging)
  timeout: 10m              # Overall timeout (supports s, m, h, d)
  allowed_commands:          # Optional: restrict which commands are permitted
    - cargo check
    - cargo clippy
    - cargo test

stages:
  - name: stage-name
    parallel: false          # Run steps in parallel? (default: false)
    steps:
      - name: step-name
        type: command         # Step type (default: command, see below)
        run: cargo check      # Shell command (for type: command)
        timeout: 60s          # Per-step timeout
        required: true        # Fail the pipeline if this step fails? (default: true)
        changeset_aware: false # Scope command to affected packages? (default: false)

Step Types

TypeDescriptionRequired Fields
commandRun a shell commandrun
semanticAST-based code analysischeck (list of check names)
agent-reviewAI code review via Claudeprompt
human-approveManual approval gate

Note: Available semantic check names include unused-imports, dead-code, type-safety, and complexity. Run dk pipeline checks --list to see all available checks for your project type.

# Example: semantic step with multiple checks
- name: analysis
  type: semantic
  check:
    - unused-imports
    - dead-code
    - type-safety
  timeout: 60s

Command Allowlist

For security, only approved shell commands can run in type: command steps. This does not affect agent-review, semantic, or human-approve step types.

With allowed_commands: Only the listed command prefixes are permitted. Use this to lock down exactly which tools run.

Without allowed_commands: The default allowlist applies:

  • cargo check, cargo test, cargo clippy, cargo fmt, cargo build
  • npm test, npm run lint, npm run check
  • bun test, bun run lint, bun run check
  • npx tsc, bunx tsc
  • pytest, python -m pytest
  • go build, go test, go vet
  • mvn test, mvn compile, gradle test, gradle build
  • dotnet build, dotnet test
  • bundle exec rake test, bundle exec rspec
  • composer test, php artisan test
  • swift build, swift test
  • sbt test, sbt compile
  • cabal build, cabal test, stack build, stack test
  • julia --project
  • shellcheck, bash -n
  • make check, make test, make lint

Shell metacharacters (;, &, |, `, $, >, <, >>, etc.) are always blocked regardless of allowlist.

Changeset-Aware Commands

When changeset_aware: true, dkod scopes commands to only the affected packages:

  • Rust: cargo test becomes cargo test -p affected-crate-1 -p affected-crate-2; cargo check becomes cargo check -p affected-crate-1 -p affected-crate-2
  • Bun/Node: bun test becomes bun test src/components src/pages; npm test becomes npm test -- src/components src/pages (directories containing changed files passed as arguments)
  • Python: pytest becomes pytest affected_package1 affected_package2 (all affected packages as positional arguments)
  • Go: go test ./... becomes go test ./affected/pkg/... ./other/pkg/...; go build ./... becomes go build ./affected/pkg/... ./other/pkg/...
  • Java (Maven): mvn test becomes mvn test -pl affected-module-1,affected-module-2
  • Java/Kotlin (Gradle): gradle test becomes gradle :affected-module:test :other-module:test
  • C#: dotnet test becomes dotnet test affected/Project.csproj then dotnet test affected/Other.csproj (dkod runs a separate invocation per affected project, since dotnet test only accepts a single project path)
  • Scala: sbt test becomes sbt affected/test other/test (where affected and other are sbt sub-project names, not filesystem paths)
  • Haskell (Cabal): cabal test becomes cabal test affected-pkg other-pkg
  • Haskell (Stack): stack test becomes stack test affected-pkg other-pkg

Any Cargo subcommand that accepts -p flags is supported. When transforming commands, any flags in the original run value are preserved — for example, pytest -v becomes pytest -v affected_package1 affected_package2.

This makes verification faster by only testing what changed.

Note: If a command does not match any of the supported transformations above, changeset_aware: true is silently treated as false and the command runs unchanged against the full project.

Examples

These are optional .dkod/pipeline.yaml configurations. You only need one if you want to customize the default behavior.

Rust Project

pipeline:
  name: rust-verify
  timeout: 5m

stages:
  - name: checks
    steps:
      - name: clippy
        run: cargo clippy
        timeout: 60s
        changeset_aware: true
        required: false

  - name: test
    steps:
      - name: test
        run: cargo test
        timeout: 3m
        changeset_aware: true

TypeScript Project (Bun)

pipeline:
  name: ts-verify
  timeout: 5m
  allowed_commands:
    - bun test
    - bun run lint
    - bunx tsc

stages:
  - name: checks
    parallel: true
    steps:
      - name: typecheck
        run: bunx tsc --noEmit
        timeout: 60s

      - name: lint
        run: bun run lint
        timeout: 30s
        required: false

  - name: test
    steps:
      - name: test
        run: bun test
        timeout: 2m
        changeset_aware: true

Python Project

pipeline:
  name: python-verify
  timeout: 5m
  allowed_commands:
    - pytest

stages:
  - name: test
    steps:
      - name: test
        run: pytest -v
        timeout: 3m
        changeset_aware: true

Go Project

pipeline:
  name: go-verify
  timeout: 5m

stages:
  - name: checks
    parallel: true
    steps:
      - name: vet
        run: go vet ./...
        timeout: 60s

      - name: test
        run: go test ./...
        timeout: 3m
        changeset_aware: true

Java Project (Maven)

pipeline:
  name: java-verify
  timeout: 10m

stages:
  - name: test
    steps:
      - name: test
        run: mvn test
        timeout: 5m
        changeset_aware: true

Kotlin Project (Gradle)

pipeline:
  name: kotlin-verify
  timeout: 10m

stages:
  - name: test
    steps:
      - name: test
        run: gradle test
        timeout: 5m
        changeset_aware: true

C# Project

pipeline:
  name: csharp-verify
  timeout: 5m

stages:
  - name: checks
    steps:
      - name: build
        run: dotnet build
        timeout: 2m

      - name: test
        run: dotnet test
        timeout: 3m
        changeset_aware: true

Ruby Project

pipeline:
  name: ruby-verify
  timeout: 5m

stages:
  - name: test
    steps:
      - name: test
        run: bundle exec rake test
        timeout: 3m

PHP Project

pipeline:
  name: php-verify
  timeout: 5m

stages:
  - name: test
    steps:
      - name: test
        run: composer test
        timeout: 3m

Swift Project

pipeline:
  name: swift-verify
  timeout: 5m

stages:
  - name: checks
    steps:
      - name: build
        run: swift build
        timeout: 2m

      - name: test
        run: swift test
        timeout: 3m

Scala Project

pipeline:
  name: scala-verify
  timeout: 10m

stages:
  - name: test
    steps:
      - name: test
        run: sbt test
        timeout: 5m
        changeset_aware: true

Haskell Project

pipeline:
  name: haskell-verify
  timeout: 10m

stages:
  - name: test
    steps:
      - name: test
        run: cabal test
        timeout: 5m
        changeset_aware: true

C / C++ Project

Tip: CMake-based projects should add two cmake steps before make test. The example below assumes a Makefile already exists. For CMake projects, add both cmake -B and cmake --build to your allowed_commands and include a cmake -B build step (configure) followed by a cmake --build build step (build) before make test.

pipeline:
  name: cpp-verify
  timeout: 5m
  allowed_commands:
    - make test
    - make check

stages:
  - name: test
    steps:
      - name: test
        run: make test
        timeout: 3m

Julia Project

pipeline:
  name: julia-verify
  timeout: 5m

stages:
  - name: test
    steps:
      - name: test
        run: julia --project test/runtests.jl
        timeout: 3m

Bash Project

pipeline:
  name: bash-verify
  timeout: 2m

stages:
  - name: checks
    parallel: true
    steps:
      - name: shellcheck
        run: shellcheck src/*.sh
        timeout: 30s

      - name: syntax
        run: bash -n src/*.sh
        timeout: 30s

Multi-Stage with AI Review

pipeline:
  name: full-verify
  timeout: 24h

stages:
  - name: checks
    parallel: false
    steps:
      - name: check
        run: cargo check
        timeout: 60s

      - name: test
        run: cargo test
        timeout: 3m
        changeset_aware: true

  - name: review
    steps:
      - name: ai-review
        type: agent-review
        prompt: Review for security vulnerabilities, error handling, and correctness
        timeout: 5m
        required: false

  - name: approval
    steps:
      - name: human-gate
        type: human-approve
        timeout: 7d

How human-approve works: When the pipeline reaches a human-approve step, it pauses and notifies the repository owner via the dkod dashboard. The changeset remains in a pending state until a team member explicitly approves or rejects it through the dashboard UI or the API (POST /api/changesets/{id}/approve or POST /api/changesets/{id}/reject). If no action is taken, the step times out according to the pipeline or step-level timeout setting. Note that the top-level pipeline.timeout acts as a hard ceiling — if it's shorter than a step's timeout, the pipeline timeout takes precedence.

Next Steps