Organizational

Taskfile as Contract

Pattern for using Taskfile as the single operational interface for repository operations, with prescriptive task naming, CI namespace, environment handling, and recipes.

Production

Pattern for using Taskfile as the single operational interface for repository operations.

Core Principle

Taskfile is the single interface for all repository operations.

If an operation exists in Taskfile, it MUST be used. Tools MUST NOT be invoked directly when a Taskfile task exists.

What is Taskfile?

Taskfile is a task runner and build tool that replaces Makefiles with a YAML-based syntax.

At Ontopix, Taskfile serves as:

  • The operational contract between developers and repositories
  • The interface for AI agents to execute tasks
  • The documentation of available operations
  • The consistency layer across all repositories

Why Taskfile Matters

For Humans

  • Single source of truth — One place to find all available operations
  • Consistent interface — Same patterns across all repositories
  • Self-documentingtask --list shows what's possible
  • Onboarding — New engineers see how to operate the repository

For AI Agents

  • Explicit operations — No guessing which commands to run
  • Safe execution — Tasks encapsulate safe, tested operations
  • Pattern adherence — Tasks encode organizational conventions
  • Reproducible — Same task produces same result

For the Organization

  • Standardization — Reduces cognitive load across repositories
  • Maintainability — Changes to tools don't break workflows
  • Discoverability — Engineers find operations through exploration
  • Enforcement — Patterns are encoded in tasks, not documentation

Task Name Terminology

Task names use colons to separate up to 3 hierarchical segments:

namespace:action          → lint:check, test:unit, sandbox:start
namespace:action:target   → deploy:lambda:mcp-erp, deps:login:npm
SegmentRoleExamples
namespaceConcern area / domainlint, test, ci, sandbox, infra, deploy, release, deps, dev
actionThe operationcheck, fix, start, plan, install, login
target (optional)Specific scope / componentnpm, mcp-erp, terraform

Rules:

  • Maximum 3 segments (typically 2, and 4 allowed in rare cases)
  • Use hyphens within a segment for multi-word names (agent-support, not agentSupport)
  • No abbreviations (integration, not int)
  • Flat tasks (no namespace) are reserved for meta/utility: default, help, clean

Standard Task Namespaces

dev:* — Developer Lifecycle

TaskLevelPurpose
dev:setupMUSTFirst-use setup (clone-to-working). Idempotent. Calls dev:install, copies .env.example.env if missing, runs validate:structure, prints next-steps.
dev:installMUSTInstall language dependencies (uv sync / pnpm install). Also useful standalone after git pull.
dev:runMUSTRun the application locally.
dev:watchSHOULDRun with hot reload (if applicable).
dev:migrateSHOULDRun database migrations (if applicable).
dev:seedSHOULDSeed development data (if applicable).

lint:* — Code Quality

TaskLevelPurpose
lint:checkMUSTCheck code quality without modifying files.
lint:fixMUSTFix auto-fixable issues.
lint:typesSHOULDRun type checking (mypy, nuxt typecheck, tsc).
lint:formatSHOULDCheck formatting only (ruff format --check, prettier --check).
lint:allMUSTComposite: lint:check + lint:types + lint:format (if more than one lint task exists).

test:* — Testing

TaskLevelPurpose
test:unitMUSTRun unit tests (if tests exist).
test:integrationSHOULDRun integration tests.
test:e2eSHOULDRun end-to-end tests.
test:allMUSTComposite: all test tasks.
test:covSHOULDRun tests with coverage report.

ci:* — CI Pipeline

Maps 1:1 to CI workflow steps. A developer runs these locally to verify what CI will run.

TaskLevelPurpose
ci:lintMUSTSame lint checks CI runs. Composes lint:all + validate:structure + infra:fmt (if applicable).
ci:testMUSTSame tests CI runs. Composes test:all.
ci:buildSHOULDSame build CI runs (if repo produces artifacts). Composes build:*.
ci:allMUSTFull CI pipeline: ci:lint + ci:test + ci:build.

Rule: if task ci:all passes locally, CI must pass. No surprises.

What ci:* is NOT for: CI-environment-specific setup (credential injection, cache config, runner provisioning). Those belong in GitHub Actions workflow YAML, not in Taskfile.

sandbox:* — Local Environment

TaskLevelPurpose
sandbox:startMUSTStart sandbox environment.
sandbox:stopMUSTStop and clean up.
sandbox:restartSHOULDRestart (rebuild + recreate).
sandbox:logsSHOULDTail logs.
sandbox:statusSHOULDShow running state.
sandbox:resetSHOULDReset to clean state.

Use start/stop, not up/down. See Sandbox Environments.

infra:* — Infrastructure

TaskLevelPurpose
infra:initMUSTInitialize Terraform.
infra:planMUSTShow Terraform plan.
infra:applyMUSTApply changes.
infra:fmtMUSTCheck formatting. MUST use terraform fmt -check -recursive.
infra:validateSHOULDValidate configuration.
infra:destroySHOULDDestroy infrastructure, with precondition guard.

deps:* — Dependency Management

TaskLevelPurpose
deps:installSHOULDInstall project dependencies.
deps:loginSHOULDAuthenticate to private registries.
deps:login:npmSHOULDCodeArtifact login for npm/pnpm (see recipes).
deps:login:pypiSHOULDCodeArtifact login for pip/uv (see recipes).
deps:updateSHOULDUpdate dependencies.
deps:outdatedSHOULDList outdated dependencies.

build:* — Build Operations

TaskLevelPurpose
build:dockerSHOULDBuild Docker image.
build:distSHOULDBuild distribution artifacts.
build:lambdaSHOULDBuild Lambda deployment package.

deploy:* — Deployment Operations

TaskLevelPurpose
deploy:devSHOULDDeploy to development environment.
deploy:stagingSHOULDDeploy to staging environment.
deploy:prodSHOULDDeploy to production (with safeguards).

release:* — Library/Artifact Release

For repos that publish libraries or packages to artifact registries (CodeArtifact, npm, PyPI). Mirrors deploy:* symmetry: deploy targets running services, release targets artifact registries.

TaskLevelPurpose
release:publishMUSTPublish built artifacts to registry.
release:versionSHOULDSync version from source of truth (e.g., VERSION file) to package manifests (package.json, pyproject.toml, etc.).
release:allSHOULDFull release pipeline: release:version + build:dist + release:publish.

release:all chains build:dist — artifacts are always rebuilt from the current version before publishing. Individual tasks remain independently callable for partial workflows.

Note: The versioning protocol itself (VERSION file as source of truth, sync mechanics, version validation across monorepo packages) is a cross-cutting concern documented in a separate pattern. This namespace prescribes only the task names and composition.

validate:* — Domain Validation

validate:* is for domain-specific conformance checks — not CI rollup (that's ci:all).

TaskLevelPurpose
validate:structureSHOULDCheck required files exist.
validate:schemasSHOULDValidate data against schemas (if applicable).
validate:configSHOULDValidate configuration files (if applicable).

config:* — Configuration Management

For repos with SSM/Secrets Manager or similar operational config.

TaskLevelPurpose
config:syncSHOULDPush local config to remote store.
config:readSHOULDRead remote config values.
config:validateSHOULDValidate config structure.

Task Naming Conventions

Use Namespaces

# Good: Namespaced
dev:run:
  desc: Run the application locally
  cmds: [...]

# Bad: Flat
run-dev:
  desc: Run the application locally
  cmds: [...]

Use Descriptive Names

# Good: Clear intent
test:integration:
  desc: Run integration tests against local database

# Bad: Ambiguous
test:int:
  desc: Run some tests

Use Consistent Patterns

# Good: Consistent check/fix pattern
lint:check:
  desc: Check code quality without modifying files

lint:fix:
  desc: Fix auto-fixable linting issues

# Bad: Inconsistent naming
lint:check:
  desc: Check code quality

lint:autofix:
  desc: Fix issues automatically

Variables and Environment

Override Precedence Chain

Taskfile defaults  →  .env file  →  shell environment (last wins)

Standard Configuration

Every Taskfile MUST use:

dotenv: ['.env']

vars:
  AWS_PROFILE: '{{.AWS_PROFILE | default "ontopix-dev"}}'
  AWS_REGION: '{{.AWS_REGION | default "eu-central-1"}}'
  ENV: '{{.ENV | default "dev"}}'

Never hardcode values that differ per developer or environment:

# Bad: hardcoded, no override possible via .env
env:
  AWS_PROFILE: ontopix-dev

# Good: overridable via .env or shell
vars:
  AWS_PROFILE: '{{.AWS_PROFILE | default "ontopix-dev"}}'

.env as Standard

  • Every repo MUST use dotenv: ['.env'] in Taskfile
  • Every repo MUST ship .env.example with all supported variables and comments
  • .env MUST be in .gitignore
  • .envrc is an optional developer convenience (direnv), not a requirement

Example .env.example:

# AWS authentication
# AWS_PROFILE=ontopix-dev

# Environment target for infra/config tasks (dev|pre|prod)
# ENV=dev

# CodeArtifact token (refreshed via: eval $(task deps:login:npm))
# NPM_TOKEN=

Environment-Scoped Tasks

Tasks requiring an environment MUST use the ENV variable with precondition:

infra:plan:
  desc: Plan infrastructure changes (ENV=dev|pre|prod)
  vars:
    ENV: '{{.ENV | default "dev"}}'
  preconditions:
    - sh: '[ -n "{{.ENV}}" ]'
      msg: "ENV is required (dev|pre|prod). Usage: ENV=dev task infra:plan"
  cmds:
    - terraform plan -var-file={{.ENV}}.tfvars

For destructive operations, omit the default to force explicit choice:

infra:destroy:
  vars:
    ENV: '{{.ENV}}'  # no default — force explicit
  preconditions:
    - sh: '[ -n "{{.ENV}}" ]'
      msg: "ENV is required"
    - sh: '[ "$CONFIRM" = "yes" ]'
      msg: "Run with CONFIRM=yes to destroy infrastructure"

Task Requirements

Every Task MUST Have

  1. Description — Via desc: field, shown in task --list
  2. Clear purpose — Single responsibility principle
  3. Idempotency — Running twice should be safe (when possible)

Tasks SHOULD Have

  1. Dependencies — Via deps: when tasks rely on each other
  2. Preconditions — Via preconditions: for validation
  3. Status checks — Via status: to skip if already done
  4. Variables — Via vars: for parameterization

Recipes

The following are proven patterns from existing Ontopix repos. Use them as starting points and adapt to your context.

CodeArtifact Login (npm/pnpm)

Outputs a token to stdout for eval, avoiding ~/.npmrc pollution. Optionally persists to .env for cross-task propagation.

deps:login:npm:
  desc: "Get CodeArtifact token for npm/pnpm — use: eval $(task deps:login:npm)"
  silent: true
  cmds:
    - |
      TOKEN=$(aws codeartifact get-authorization-token \
        --domain ontopix \
        --region {{.AWS_REGION}} \
        --query authorizationToken \
        --output text)
      echo "export NPM_TOKEN=$TOKEN"
  preconditions:
    - sh: command -v aws
      msg: "AWS CLI is required"

deps:login:npm:env:
  desc: "Refresh CodeArtifact token and persist to .env"
  silent: true
  cmds:
    - |
      TOKEN=$(aws codeartifact get-authorization-token \
        --domain ontopix \
        --region {{.AWS_REGION}} \
        --query authorizationToken \
        --output text)
      if grep -q '^NPM_TOKEN=' .env 2>/dev/null; then
        sed -i '' "s|^NPM_TOKEN=.*|NPM_TOKEN=$TOKEN|" .env
      else
        echo "NPM_TOKEN=$TOKEN" >> .env
      fi
      echo "NPM_TOKEN refreshed in .env"
  preconditions:
    - sh: command -v aws
      msg: "AWS CLI is required"
    - sh: '[ -f .env ]'
      msg: ".env file not found. Run: cp .env.example .env"

Usage:

  • Quick (shell session): eval $(task deps:login:npm)
  • Persistent (across tasks): task deps:login:npm:env

Forbidden: aws codeartifact login --tool npm — writes to ~/.npmrc and pollutes system-wide npm config.

CodeArtifact Login (pip/uv)

deps:login:pypi:
  desc: "Get CodeArtifact token for pip/uv — use: eval $(task deps:login:pypi)"
  silent: true
  cmds:
    - |
      TOKEN=$(aws codeartifact get-authorization-token \
        --domain ontopix \
        --region {{.AWS_REGION}} \
        --query authorizationToken \
        --output text)
      REPO_URL="https://aws:${TOKEN}@ontopix-${AWS_ACCOUNT_ID}.d.codeartifact.{{.AWS_REGION}}.amazonaws.com/pypi/pypi/simple/"
      echo "export UV_EXTRA_INDEX_URL=$REPO_URL"
  preconditions:
    - sh: command -v aws
      msg: "AWS CLI is required"

Developer Setup (clone-to-working)

dev:setup:
  desc: "First-use setup: install deps, configure env, validate structure"
  cmds:
    - |
      if [ ! -f .env ]; then
        cp .env.example .env
        echo "Created .env from .env.example — review and adjust values."
      fi
    - task: dev:install
    - task: validate:structure
    - echo ""
    - echo "Setup complete. Next steps:"
    - echo "  1. Review .env and set any required values"
    - echo "  2. task dev:run    — start the application"
    - echo "  3. task --list     — see all available operations"

dev:install:
  desc: "Install project dependencies"
  cmds:
    - uv sync  # or: pnpm install

CI Pipeline (local mirror)

ci:lint:
  desc: "Run all CI lint checks"
  cmds:
    - task: lint:all
    - task: validate:structure

ci:test:
  desc: "Run all CI tests"
  cmds:
    - task: test:all

ci:all:
  desc: "Run full CI pipeline locally"
  cmds:
    - task: ci:lint
    - task: ci:test

For repos that produce build artifacts, add ci:build:

ci:build:
  desc: "Run CI build step"
  cmds:
    - task: build:dist

ci:all:
  desc: "Run full CI pipeline locally"
  cmds:
    - task: ci:lint
    - task: ci:test
    - task: ci:build

Library Release Pipeline

For repos that publish packages to artifact registries. Chains version sync, build, and publish into a single release:all rollup.

release:version:
  desc: "Sync VERSION to package manifests and rebuild"
  cmds:
    - |
      VERSION=$(cat VERSION)
      echo "Syncing version ${VERSION}..."
      # Adapt to your package types:
      # - sed for pyproject.toml
      # - jq/sed for package.json
      sed -i '' 's/^version = .*/version = "'"${VERSION}"'"/' pyproject.toml
    - task: build:dist

release:publish:
  desc: "Publish built artifacts to registry"
  cmds:
    - task: deps:login:npm  # or deps:login:pypi
    - |
      # Adapt to your registry and package layout:
      for pkg in {{.PACKAGES}}; do
        echo "Publishing ${pkg}..."
        pnpm publish --no-git-checks {{.PACKAGES_DIR}}/${pkg}
      done
  preconditions:
    - sh: test -d {{.PACKAGES_DIR}}
      msg: "Packages not built. Run 'task build:dist' first."

release:all:
  desc: "Full release pipeline: sync version, build, publish"
  cmds:
    - task: release:version
    - task: release:publish

Adapt the sync and publish commands to match your stack (pnpm, uv, twine, etc.). See stylebook for a monorepo example publishing multiple npm packages to CodeArtifact.

Taskfile Composition via includes:

For large Taskfiles, Taskfile's includes: feature allows splitting by concern. Tasks in included files are auto-namespaced.

# Taskfile.yaml
version: '3'
dotenv: ['.env']

includes:
  infra:
    taskfile: .infra/Taskfile.yaml
    dir: .infra
  ci:
    taskfile: ./taskfiles/ci.yaml

A task plan: in .infra/Taskfile.yaml becomes infra:plan automatically.

Monorepo Delegation

Root Taskfile orchestrates; project Taskfiles own their operations:

ci:all:
  desc: "Run CI across all projects"
  cmds:
    - for:
        - projects/libs/config
        - projects/apps/my-service
      cmd: echo "── {{.ITEM}} ──" && task -d {{.ITEM}} ci:all

Each project MUST expose ci:all (at minimum) so the root can delegate uniformly.

What MUST Go in Taskfile

Operations that MUST be defined as Taskfile tasks:

  • Running the application
  • Running tests
  • Running linters
  • Building artifacts
  • Starting/stopping sandboxes
  • Infrastructure operations
  • Database migrations
  • Deployment operations
  • CI pipeline checks

What SHOULD NOT Go in Taskfile

Operations that SHOULD NOT be in Taskfile:

  • One-off debugging commands (unless they're reusable)
  • Personal developer scripts (unless useful to everyone)
  • Operations that are better handled by language-specific tools (e.g., npm test is fine, no need for task test:unit wrapper unless it adds value)

AI Agent Contract

Agents MUST use Taskfile tasks when they exist.

If an agent needs to:

  • Run tests → Use task test:*
  • Start a sandbox → Use task sandbox:start
  • Apply infrastructure → Use task infra:apply
  • Verify CI checks → Use task ci:all

Agents MUST NOT:

  • Invoke tools directly when a task exists (e.g., pytest instead of task test:unit)
  • Assume task behavior without checking task --list
  • Skip tasks because they "know better"

Agents SHOULD:

  • Run task --list to discover available operations
  • Read task descriptions to understand intent
  • Use tasks as the primary interface to the repository

Documentation in AGENTS.md

Every repository's AGENTS.md MUST document:

  • Which Taskfile tasks are available
  • What each task does (or reference task --list)
  • Any prerequisites for running tasks
  • Which tasks agents should use for common operations

Cross-Repository Consistency

Taskfile enables consistency across repositories:

OperationEvery Repository
First-use setuptask dev:setup
Run teststask test:all
Start sandboxtask sandbox:start
Check code qualitytask lint:check
Run locallytask dev:run
Full CI checktask ci:all
Publish librarytask release:publish

This means:

  • Engineers know how to operate any repository
  • AI agents have a uniform interface
  • Onboarding is faster
  • Documentation is self-evident

Rationale

Taskfile as a contract exists because:

  • Humans and agents need a consistent interface
  • Operations should be discoverable and self-documenting
  • Organizational patterns should be encoded, not just documented
  • Repositories should be immediately operable by any engineer

See ADR-0003 for the original decision and ADR-0013 for the prescriptive naming and recipes extension.