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.
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-documenting —
task --listshows 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
| Segment | Role | Examples |
|---|---|---|
| namespace | Concern area / domain | lint, test, ci, sandbox, infra, deploy, release, deps, dev |
| action | The operation | check, fix, start, plan, install, login |
| target (optional) | Specific scope / component | npm, 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, notagentSupport) - No abbreviations (
integration, notint) - Flat tasks (no namespace) are reserved for meta/utility:
default,help,clean
Standard Task Namespaces
dev:* — Developer Lifecycle
| Task | Level | Purpose |
|---|---|---|
dev:setup | MUST | First-use setup (clone-to-working). Idempotent. Calls dev:install, copies .env.example → .env if missing, runs validate:structure, prints next-steps. |
dev:install | MUST | Install language dependencies (uv sync / pnpm install). Also useful standalone after git pull. |
dev:run | MUST | Run the application locally. |
dev:watch | SHOULD | Run with hot reload (if applicable). |
dev:migrate | SHOULD | Run database migrations (if applicable). |
dev:seed | SHOULD | Seed development data (if applicable). |
lint:* — Code Quality
| Task | Level | Purpose |
|---|---|---|
lint:check | MUST | Check code quality without modifying files. |
lint:fix | MUST | Fix auto-fixable issues. |
lint:types | SHOULD | Run type checking (mypy, nuxt typecheck, tsc). |
lint:format | SHOULD | Check formatting only (ruff format --check, prettier --check). |
lint:all | MUST | Composite: lint:check + lint:types + lint:format (if more than one lint task exists). |
test:* — Testing
| Task | Level | Purpose |
|---|---|---|
test:unit | MUST | Run unit tests (if tests exist). |
test:integration | SHOULD | Run integration tests. |
test:e2e | SHOULD | Run end-to-end tests. |
test:all | MUST | Composite: all test tasks. |
test:cov | SHOULD | Run 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.
| Task | Level | Purpose |
|---|---|---|
ci:lint | MUST | Same lint checks CI runs. Composes lint:all + validate:structure + infra:fmt (if applicable). |
ci:test | MUST | Same tests CI runs. Composes test:all. |
ci:build | SHOULD | Same build CI runs (if repo produces artifacts). Composes build:*. |
ci:all | MUST | Full 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
| Task | Level | Purpose |
|---|---|---|
sandbox:start | MUST | Start sandbox environment. |
sandbox:stop | MUST | Stop and clean up. |
sandbox:restart | SHOULD | Restart (rebuild + recreate). |
sandbox:logs | SHOULD | Tail logs. |
sandbox:status | SHOULD | Show running state. |
sandbox:reset | SHOULD | Reset to clean state. |
Use start/stop, not up/down. See Sandbox Environments.
infra:* — Infrastructure
| Task | Level | Purpose |
|---|---|---|
infra:init | MUST | Initialize Terraform. |
infra:plan | MUST | Show Terraform plan. |
infra:apply | MUST | Apply changes. |
infra:fmt | MUST | Check formatting. MUST use terraform fmt -check -recursive. |
infra:validate | SHOULD | Validate configuration. |
infra:destroy | SHOULD | Destroy infrastructure, with precondition guard. |
deps:* — Dependency Management
| Task | Level | Purpose |
|---|---|---|
deps:install | SHOULD | Install project dependencies. |
deps:login | SHOULD | Authenticate to private registries. |
deps:login:npm | SHOULD | CodeArtifact login for npm/pnpm (see recipes). |
deps:login:pypi | SHOULD | CodeArtifact login for pip/uv (see recipes). |
deps:update | SHOULD | Update dependencies. |
deps:outdated | SHOULD | List outdated dependencies. |
build:* — Build Operations
| Task | Level | Purpose |
|---|---|---|
build:docker | SHOULD | Build Docker image. |
build:dist | SHOULD | Build distribution artifacts. |
build:lambda | SHOULD | Build Lambda deployment package. |
deploy:* — Deployment Operations
| Task | Level | Purpose |
|---|---|---|
deploy:dev | SHOULD | Deploy to development environment. |
deploy:staging | SHOULD | Deploy to staging environment. |
deploy:prod | SHOULD | Deploy 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.
| Task | Level | Purpose |
|---|---|---|
release:publish | MUST | Publish built artifacts to registry. |
release:version | SHOULD | Sync version from source of truth (e.g., VERSION file) to package manifests (package.json, pyproject.toml, etc.). |
release:all | SHOULD | Full 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).
| Task | Level | Purpose |
|---|---|---|
validate:structure | SHOULD | Check required files exist. |
validate:schemas | SHOULD | Validate data against schemas (if applicable). |
validate:config | SHOULD | Validate configuration files (if applicable). |
config:* — Configuration Management
For repos with SSM/Secrets Manager or similar operational config.
| Task | Level | Purpose |
|---|---|---|
config:sync | SHOULD | Push local config to remote store. |
config:read | SHOULD | Read remote config values. |
config:validate | SHOULD | Validate 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.examplewith all supported variables and comments .envMUST be in.gitignore.envrcis 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
- Description — Via
desc:field, shown intask --list - Clear purpose — Single responsibility principle
- Idempotency — Running twice should be safe (when possible)
Tasks SHOULD Have
- Dependencies — Via
deps:when tasks rely on each other - Preconditions — Via
preconditions:for validation - Status checks — Via
status:to skip if already done - 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 testis fine, no need fortask test:unitwrapper 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.,
pytestinstead oftask test:unit) - Assume task behavior without checking
task --list - Skip tasks because they "know better"
Agents SHOULD:
- Run
task --listto 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:
| Operation | Every Repository |
|---|---|
| First-use setup | task dev:setup |
| Run tests | task test:all |
| Start sandbox | task sandbox:start |
| Check code quality | task lint:check |
| Run locally | task dev:run |
| Full CI check | task ci:all |
| Publish library | task release:publish |
This means:
- Engineers know how to operate any repository
- AI agents have a uniform interface
- Onboarding is faster
- Documentation is self-evident
Related Patterns
- AI Agent Entrypoint — How agents use Taskfile
- Sandbox Environments — Sandbox tasks in Taskfile
- Repository Structure — Taskfile as required file
- Infrastructure Layout — Infrastructure tasks in Taskfile
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.