skills/writing-justfiles/SKILL.md
Create, edit, and maintain `justfile` automation for any codebase using the `just` command runner. Trigger whenever a request involves creating a new `justfile`, modifying/updating an existing `justfile`, adding or changing recipes, refactoring `justfile` structure, or automating project commands via `just`.
npx skillsauth add neodejack/skills writing-justfilesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Guide for creating well-structured justfile recipes using best practices from the official Just manual and community conventions.
just --list output."{{arg}}".justfile (lowercase preferred).just finds it from any subdirectory.# build the project
build:
cargo build --release
# deploy to a target environment
deploy env='staging':
./scripts/deploy.sh "{{env}}"
{{parameter}} interpolations inside shell commands.$ to export them as environment variables: foo $bar:.For non-trivial logic, use [script] so all lines run as a single script (sharing state), unlike normal recipes where each line is a separate shell invocation:
# run database migration
[script('bash')]
migrate:
set -euo pipefail
echo "Running migrations..."
./manage.py migrate
set -euo pipefail at the top of bash script recipes for strict error handling.[script] over shebang (#!/usr/bin/env bash) recipes for better cross-platform compatibility.# run all checks then deploy
release: test lint
echo "Releasing..."
# run tests
test:
cargo test
# run linter
lint:
cargo clippy
&& for dependencies that must all succeed before the recipe body runs: release: test && lint.[parallel] attribute to run dependencies concurrently when they are independent.Apply attributes above recipe definitions:
# organize into groups for `just --list`
[group('build')]
build:
cargo build
# hide helper recipes from listings
[private]
_setup:
echo "internal setup"
# stay in the invocation directory
[no-cd]
commit file:
git add "{{file}}"
git commit
# require user confirmation before destructive actions
[confirm]
clean:
rm -rf target/
# platform-specific recipes
[macos]
install-deps:
brew install hugo
[linux]
install-deps:
apt-get install hugo
_ as a shorthand for [private].[no-cd] for recipes that operate relative to the caller's working directory.[confirm] for destructive or irreversible operations.[no-exit-message] on helper recipes that may exit non-zero intentionally (e.g., user cancellation or precondition checks), to suppress just's default error output.[script])The [script] attribute (v1.33.0+) runs a recipe as a single script, like shebang recipes but without needing a shebang line. It avoids cross-platform shebang issues (e.g., cygpath on Windows, inconsistent shebang splitting across OSes).
# plain [script] uses the script-interpreter setting (default: sh -eu)
[script]
process:
echo "step 1"
echo "step 2"
# [script(COMMAND)] specifies the interpreter explicitly
[script('bash')]
complex:
set -euo pipefail
for f in *.txt; do
echo "Processing $f"
done
# use with python or other languages
[script('python3')]
analyze:
import json
data = json.loads('{"key": "value"}')
print(data["key"])
[script] (no argument) uses set script-interpreter (defaults to sh -eu), not set shell.[script('command')] specifies the interpreter directly.[script] over shebang recipes for better cross-platform compatibility.set script-interpreter := ['bash', '-euo', 'pipefail']
Declare settings at the top of the justfile:
# load .env file automatically
set dotenv-load := true
# use bash instead of sh
set shell := ["bash", "-cu"]
# suppress command echoing by default
set quiet := true
# search parent directories for recipes not found locally
set fallback := true
# assign variables
version := "1.0.0"
release_branch := "main"
# use built-in functions
home := env_var('HOME')
os_name := os()
arch_name := arch()
project_dir := justfile_directory()
# conditional expressions
profile := if env_var_or_default('CI', '') != '' { "release" } else { "debug" }
Make the first recipe a help/list command so bare just shows available recipes:
# list available recipes
[default]
default:
@just --list --list-heading $'just do what?\n' --list-prefix '~> '
Always use this exact default recipe to provide a friendly listing.
Split into modules when the justfile grows large:
import? '.just/build.just'
import? '.just/deploy.just'
import? '.just/test.just'
? to make imports optional (won't error if file is missing)..just/ directory for module files.justfile # main orchestration
.just/
build.just # build commands
deploy.just # deployment recipes
test.just # test recipes
[private, no-cd, no-exit-message, script('bash')]
_on-branch:
if [[ $(git rev-parse --abbrev-ref HEAD) == "main" ]]; then
echo "Error: must be on a feature branch" >&2
exit 1
fi
# create a pull request (requires feature branch)
pr: _on-branch
gh pr create --fill
Provide a consistent set of recipe names across all projects:
# start the dev server
run:
<project-specific command>
# run the test suite
test:
<project-specific command>
# format source code
format:
<project-specific command>
# run linters and type checks
check:
<project-specific command>
[no-cd]
@utcdate:
TZ=UTC date +"%Y-%m-%d"
# create a dated feature branch
[script('bash')]
branch name:
NOW=$(just utcdate)
git checkout -b "$USER/$NOW-{{name}}"
Prefix a line with @ to hide the command itself (only show output):
version:
@echo "v1.2.3"
just --fmt).just --fmt --unstable to auto-format the justfile.just --fmt --check --unstable to verify formatting. If it reports errors, run just --fmt --unstable to fix them automatically.just --dry-run <recipe> — preview commands without executing.just --verbose <recipe> — print each command before running it.just --list — show all available recipes with descriptions.just --evaluate — print all variable values.[no-cd] to change this.[script] recipes). Variables set on one line are not available on the next — use [script] to share state across lines.{{variable}} for just interpolation, not $variable (which is shell expansion).$(...) and $var. Avoid over-escaping with $$(...)/$$var unless you explicitly need a literal $ in the final command."{{arg}}" prevents word-splitting on spaces or special characters.development
Write documentation for Elixir modules, functions, types, and callbacks following official Elixir conventions. Use when asked to document Elixir code, add @moduledoc/@doc/@typedoc, write doctests, or improve Elixir documentation. Triggers on: document this elixir module, add elixir docs, write moduledoc, add doctests.
tools
Update a Homebrew tap formula to the latest GitHub Release assets using gh CLI, including verifying the tap repo, locating the formula, computing sha256 for release binaries, and committing/pushing changes. Use when asked to bump a Homebrew formula version to the latest release in a personal tap.
development
Bootstrap or improve harness-engineering scaffolding for an existing software repository so agents can work safely and productively. Use when asked to make a repo more agent-friendly, adopt harness engineering, prepare a codebase for Codex or mixed human and agent coding, add or improve repo-local guidance such as `AGENTS.md` or `ARCHITECTURE.md`, establish canonical setup/lint/typecheck/test commands, or audit a repository for missing agent workflows and verification rails.
development
Generate or refine repository-local product specification documents for proposed features through interactive discussion with the user. Use when a user describes a feature, workflow, or product behavior they want to build and Codex should ask clarifying questions, define scope, goals, user flows, requirements, and acceptance criteria, then save the result under `docs/product-specs/` such as `docs/product-specs/feature-name.md`. Also use when asked to spec out a feature, write a product spec, turn an idea into a spec, or update an existing product-spec doc.