| name | taskfile-authoring |
| description | Use when the user wants to author Taskfile content: creating new Taskfiles, adding new tasks, writing task dependencies/preconditions/variables, or restructuring tasks across files. The user might say "add a [name] task", "set up tasks for", "create a Taskfile", "where should this task go", "convert my Makefile", or simply describe task names and commands they want automated - even without saying "Taskfile" explicitly. Covers all ecosystems (cargo, yarn, wasm-pack, dotnet, cmake) and all scenarios: single projects, monorepos with nested includes, gitignored local overrides vs. shared dist files. Skip for: explaining how existing tasks work, debugging task run failures, or general go-task concept questions.
|
| version | 2.2.0 |
| tags | ["taskfile","task","go-task","authoring","create","update"] |
| execution | {"plan":{"allow":["inspect existing Taskfiles","inspect .gitignore","draft new tasks or Taskfiles"],"deny":["create or mutate files"]},"code":{"allow":["create new Taskfiles","update existing Taskfiles","add tasks to existing Taskfiles"]}} |
Taskfile Authoring
Prime Directives
1. Check for existing files before creating anything.
2. Use the canonical vocabulary so every project speaks the same language:
build, test, fmt, lint, lint:fix, bench, clean, run, dev, docs, check, ci, fuzz
— regardless of whether the underlying tool is cargo, yarn, dotnet, Maven, cmake, or anything else.
File Selection — Do This First
Step 1: Search for existing Taskfiles
Check in this order:
Taskfile.yaml
Taskfile.yml
Taskfile.dist.yaml
Taskfile.dist.yml
If any exist: update it. Do not create a new file alongside it.
Exception: if Taskfile.yaml or Taskfile.yml exists AND is in .gitignore, it is a
local user override. For new tracked project behavior, create Taskfile.dist.yaml.
If the user wants to extend the gitignored file itself, update it directly — no new file.
Step 2: Choose a filename for new files
When no Taskfile exists yet, use this preference:
| Preference | Filename | Use when |
|---|
| 1st | Taskfile.dist.yaml | Default for all new tracked files |
| 2nd | Taskfile.dist.yml | Only if project uses .yml convention |
| 3rd | Taskfile.yaml | Only if project explicitly avoids .dist. |
| 4th | Taskfile.yml | Last resort |
Default to Taskfile.dist.yaml unless the directory already uses a different convention.
gitignore decision table
| Situation | Action |
|---|
| No Taskfile exists | Create Taskfile.dist.yaml |
Taskfile.yaml exists, not gitignored | Update Taskfile.yaml |
Taskfile.dist.yaml exists | Update Taskfile.dist.yaml |
Taskfile.yaml gitignored, user wants tracked behavior | Create Taskfile.dist.yaml |
Taskfile.yaml gitignored, user wants to extend it | Update the gitignored file — no new file |
| Both dist and non-dist exist | Update the one matching user intent |
Minimum Required Structure
Every Taskfile needs at minimum:
version: '3'
tasks:
default:
cmds:
- task --list --sort=none
silent: true
Every user-facing task must have desc. Every task requiring external tools must have
preconditions. Never write a task that silently fails due to a missing tool.
Canonical Vocabulary
Use these names as the primary entry points. Contributors expect them regardless of ecosystem:
| Name | Purpose | Ecosystem examples |
|---|
build | Compile/bundle | cargo build, yarn build, dotnet build |
run | Run the app / backend | cargo run, dotnet run |
dev | Start dev server | yarn dev, npm run dev, vite |
test | Run test suite | cargo test, yarn test, pytest |
fmt | Format code | cargo fmt, yarn format, gofmt |
lint | Lint (read-only) | cargo clippy, yarn lint, eslint |
lint:fix | Lint + auto-fix | cargo clippy --fix, yarn lint --fix |
bench | Run benchmarks | cargo bench, vitest bench |
check | All static checks | fmt check + typecheck + lint |
ci | Full CI sequence | check + test |
clean | Remove build output | cargo clean, rm -rf dist |
docs | Build docs | cargo doc, typedoc |
docs:open | Build and open docs | same with --open |
fuzz | Run fuzzer (requires nightly in Rust) | cargo +nightly fuzz run |
fuzz:list | List fuzz targets | cargo +nightly fuzz list |
fuzz:smoke | Short smoke run of all targets | fuzz run with time limit |
When fuzz targets exist, add - task: fuzz:build as an extra command (not dep) at the end of build — so the main codebase builds first, then fuzz targets are verified to still compile. Fuzz targets that can't compile are silent breakage; this surfaces it at build time.
tasks:
build:
cmds:
- cargo build --workspace --all-targets
- task: fuzz:build
Add aliases when users might type alternate names (format for fmt, fix for lint:fix).
Naming Conventions
Namespaced tasks for specialized operations
build:release, build:timing
test:docs, test:api, test:e2e, test:component
bench:*, bench:baseline
docs:open
fuzz:list, fuzz:run, fuzz:smoke, fuzz:build, fuzz:clean
docker:build, docker:up, docker:down
db:setup, db:backup, db:restore
migrate:add, migrate:up, migrate:down
Internal helpers
Mark with internal: true to hide from task --list:
tasks:
db:er:run:
internal: true
cmds: [...]
db:er:svg:
desc: Generate ER diagram SVG
cmds:
- task: db:er:run
vars: {FORMAT: svg}
Precondition Patterns
Write preconditions that tell the user exactly what to install, not just that something is missing.
Tool availability
preconditions:
- sh: command -v cargo
msg: "cargo not found — install Rust from https://rustup.rs/"
- sh: command -v yarn
msg: "yarn not found — install Node.js and run: corepack enable"
- sh: command -v docker
msg: "docker not found — install from https://docker.com"
- sh: docker info >/dev/null 2>&1
msg: "docker daemon not running — start Docker Desktop or dockerd"
- sh: command -v cargo-nextest
msg: "cargo-nextest not found — cargo install cargo-nextest"
- sh: command -v sqlx
msg: "sqlx not found — cargo install sqlx-cli"
- sh: command -v cargo-fuzz
msg: "cargo-fuzz not found — cargo install cargo-fuzz"
- sh: rustup toolchain list | grep -q nightly
msg: "Rust nightly toolchain required — rustup toolchain install nightly"
Environment variables
preconditions:
- sh: test -n "$DATABASE_URL"
msg: "DATABASE_URL not set — copy .env.example to .env.local and fill in values"
- sh: test -f .env.local
msg: ".env.local not found — copy .env.example to .env.local"
Variable Patterns
Profile / mode switching
vars:
PROFILE: '{{ .PROFILE | default "debug" }}'
PROFILE_FLAG: '{{ if eq .PROFILE "release" }}--release{{ end }}'
PROFILE_DIR: '{{ if eq .PROFILE "release" }}release{{ else }}debug{{ end }}'
Usage: task build PROFILE=release
Package manager — hard-coded
vars:
RUN: "yarn"
tasks:
dev:
cmds:
- '{{.RUN}} dev'
Package manager — detected from lockfiles
vars:
PKG:
sh: |
if [ -f yarn.lock ]; then echo yarn
elif [ -f pnpm-lock.yaml ]; then echo pnpm run
else echo npm run; fi
Shell-computed values
vars:
GIT_SHA:
sh: git rev-parse --short HEAD
TS:
sh: date +%Y%m%d-%H%M%S
CLI args passthrough
tasks:
test:
cmds:
- cargo test {{.CLI_ARGS}}
Usage: task test -- --test-filter auth
Env Files
dotenv:
- .env.local
- .env
Earlier files win. .env.local overrides .env.
Incremental Builds
tasks:
build:
sources: [Cargo.toml, Cargo.lock, src/**/*.rs]
generates: [target/{{.PROFILE_DIR}}/myapp]
cmds: [cargo build {{.PROFILE_FLAG}}]
clean:
status:
- test ! -d target
cmds: [cargo clean]
Required Variables
tasks:
build:
requires:
vars:
- name: PROFILE
enum: [debug, release]
Destructive Tasks
tasks:
db:drop:
prompt: "This will drop the database. Continue?"
cmds: [dropdb myapp_dev]
Nested Taskfiles (includes)
includes:
web:
taskfile: web/Taskfile.dist.yaml
dir: web
tasks:
clean:
deps: [web:clean]
cmds: [cargo clean]
build:
deps: [web:build]
cmds: [cargo build]
Sub-Taskfiles should be self-contained — they must work standalone from their own directory.
Use the same canonical vocabulary (build, test, fmt) in sub-Taskfiles as in the root.
Converting from Makefile / Justfile
| Concept | Makefile | Justfile | Task |
|---|
| Default target | First rule | @default | default task |
| Phony target | .PHONY: | Automatic | All tasks are phony |
| Variable | VAR ?= val | var := val | vars: VAR: '{{ .VAR | default "val" }}' |
| Dependency | build: fmt lint | build: fmt lint | deps: [fmt, lint] |
| Env var | export FOO=bar | export FOO := bar | env: FOO: bar |
| Include | include other.mk | import 'other.just' | includes: other: taskfile: other.yaml |
| Conditional skip | Timestamp comparison | n/a | sources/generates or status |
Reference
- Official docs: https://taskfile.dev
- API reference: https://taskfile.dev/api/
- Quick reference:
docs/cheatsheet.md (bundled with this skill)
- Style examples:
examples/ (bundled with this skill)
examples/rust-workspace/ — Rust workspace with nextest, sqlx, Docker
examples/rust-simple/ — simple Rust project
examples/rust-fuzz/ — nested fuzz Taskfile (nightly toolchain, cargo-fuzz)
examples/web-yarn/ — Next.js/yarn project (note: yarn, not npm)
examples/monorepo/ — root with web sub-project via includes
examples/local-override/ — local Taskfile.yaml override pattern