justfile — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited justfile (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Generate idiomatic justfiles for any project. Replace Makefiles and ad-hoc shell scripts with a clean, discoverable command runner.
.PHONY hacks — all recipes are commands, not file targetsjust --list gives instant discoverability with doc commentsFor the official feature rationale — the most-used features, the special use cases that make justfiles truly worth it, and a deep-linked chapter index — see references/manual.md, grounded in the Just Programmer's Manual.
Every justfile this skill generates exposes one command an agent runs after every change — just build — and its no-autofix CI twin just ci. build is the default recipe. Both run the full gate (format/lint → typecheck → test → coverage) and compact their output: ✓ <step> per passing step, full tool output with file:line only on failure.
This is the default posture for every generated justfile, not an opt-in — unless the user explicitly asks for a different layout. The rationale (agents converge faster with one consistent command, deterministic pass/fail, and compact error-only output — with citations) and the full recipe template, _gate helper, and the mandatory AGENTS.md / CI updates all live in references/agent-build.md. Read it before writing any justfile.
build = the gate (autofix on). ci = the gate (autofix off). The recipe that compiles or packages an artifact is named dist (or release) — never build, which is reserved for the gate.
Scan the project root for ecosystem markers:
| File | Ecosystem | Reference |
|---|---|---|
Cargo.toml | Rust | references/rust.md |
pyproject.toml, setup.py, uv.lock | Python | references/python.md |
package.json | TypeScript/JS | references/typescript.md |
go.mod | Go | references/go.md |
Gemfile | Ruby | references/ruby.md |
Read the relevant reference file for language-specific recipes.
If multiple markers exist (e.g., a Rust backend + TypeScript frontend), combine patterns. Use modules (mod frontend, mod backend) for true monorepos — the root build gate then aggregates each module's gate so just build still means "verify everything." For the full module/import composition pattern (and when to pick import over mod), see references/composition.md.
Multi-ecosystem naming: When a project has multiple languages (e.g., Tauri with Rust + TypeScript), use ecosystem suffixes to disambiguate overlapping concerns:
test-rust, test-ts (not generic test that hides what runs)fmt-rust, fmt-ts (each ecosystem's formatter)lint-rust (clippy), lint-ts (eslint/biome)test: test-rust test-ts, fmt: fmt-rust fmt-tsdev, build, cleanLook for Makefile, Taskfile.yml, Rakefile, package.json scripts, or shell scripts in scripts/ or bin/. If found:
file-target dependency tracking for actual build artifacts)
Place justfile in the project root. Follow these conventions:
Structure order:
set dotenv-load, set shell)default: build (the canonical gate). Use @just --listonly when the user explicitly wants no default gate.
build (autofix) + ci (no autofix) + the _gate helper — seereferences/agent-build.md
_prefixed or [private])Recipe naming:
test-coverage, build-releasebuild, test, lint, deploy (not builder, tests)db-migrate, db-seed, db-reset--listDoc comments: Every public recipe gets a comment on the line above it — this is what just --list displays:
# Run the full test suite
test *args:
cargo test {{args}}Parameters:
test filter=""run *args+args (1+ required) sparinglyjust build --target x86_64), use the[arg()] attribute (v1.46+ — see "Recipe argument flags" below)
Aliases: Add a one-letter alias for any recipe a developer will type more than a few times a day. Define it on the line below the recipe so just --list shows the canonical name first:
# Run tests
test *args:
cargo test {{args}}
alias t := testDon't manufacture aliases for every recipe — only the hot path.
Settings to always include:
set dotenv-load
set shell := ["bash", "-euo", "pipefail", "-c"]-euo pipefail makes shell recipes fail loudly on any error, unset variable, or broken pipe — the same posture as a well-written bash script. If a recipe needs softer behavior, use - line prefix to ignore errors on a single command, not weaker shell flags. Skip the set shell line for Windows targets or projects without a bash dependency — the default /bin/sh is fine for most recipes.
set unstable is no longer needed for the common features: modules (stable since just 1.31), the [script] attribute (1.44), and [arg()] flags (1.46) all work on a current just without it. Add set unstable only to support a just older than the relevant stabilization.
dotenv hardening: set dotenv-load silently does nothing when .env is absent. When a project genuinely requires its .env (DB URLs, API keys), make that failure loud — fail fast rather than running with empty vars:
set dotenv-load
set dotenv-required # error if no dotenv file is found
# Pick ONE of the following (omit both to load ./.env):
set dotenv-path := ".env.local" # explicit path (relative to justfile), OR…
# set dotenv-filename := ".env.dev" # …a filename to search for up the treedotenv-required turns a missing env file into a hard error instead of a silent no-op — the "fail fast and loud" posture. Use dotenv-path to point at one exact file, or dotenv-filename to change which name just walks the tree looking for. See Settings § dotenv.
require()When a recipe depends on a tool that isn't a standard system command, declare it at the top of the justfile so just --evaluate fails fast when it's missing:
# https://github.com/jqlang/jq
jq := require("jq")
# https://github.com/casey/just (yes, just itself)
just := require("just")Use {{ jq }} to invoke through the resolved path, or call jq normally — require() has already confirmed it exists at evaluate time, so either form works. The URL comment doubles as the install hint. Skip require() for ubiquitous tools like git, curl, or the language toolchain — declare only what a fresh machine might be missing.
build and ci run through the _gate helper from references/agent-build.md, which is already compacted: ✓ <step> per passing step, full tool output (with file:line) only on failure. Coverage runs inside the gate — its table is swallowed on success and shown in full when the threshold fails. (This supersedes the old "skip coverage to save tokens" advice: keep coverage in the gate; let compaction, not omission, handle the token cost.)
For the granular recipes the gate composes, still apply:
--no-fund` to npm to drop banner/audit/fund noise.
step calls in_gate for rtk test / rtk err to get per-tool smart filtering on top of quiet-on-success. See references/rtk.md for the shell-wrap pattern and the npm script-naming gotcha. rtk is an upgrade, never a requirement — the bash step helper is the portable floor.
Generating the recipes is only half the job — the value of one canonical command is lost if nothing tells agents to use it. After creating the justfile, always do both updates. Full snippets live in references/agent-build.md.
Agent context file (AGENTS.md, CLAUDE.md, GEMINI.md, .cursor/rules, or whatever the harness uses; create AGENTS.md if none exists) — add/replace a build section that tells the agent to ALWAYS run the one command and treat failure as a hard stop:
## Build & verification
This project has ONE canonical verification command. ALWAYS run it after
changing code, and treat a non-zero exit as a hard stop.
- `just build` — autofix, then lint, typecheck, test, and coverage. Run after
every change.
- `just ci` — the same gate with no autofixes; exactly what CI runs.
Output is compacted: `✓ <step>` on success, full output (file:line) only on
failure. Don't invent ad-hoc test/lint commands — run `just build`.CI workflow — point the existing verify step at just ci so local and CI run the identical gate. This skill doesn't design pipelines; it only redirects the verify step (e.g. replace separate lint/test/coverage steps with a single run: just ci). See references/agent-build.md for the Actions snippet.
README.md — Add a "Development" or "Getting Started" section:
## Development
### Prerequisites
- [just](https://github.com/casey/just) — `brew install just` / `cargo install just`
### Quick Startjust install # Install dependencies just test # Run tests just # See all available commands
Don't duplicate the full recipe list — just --list is self-documenting.
| Makefile | justfile |
|---|---|
.PHONY: target | (not needed) |
$(VAR) | {{var}} |
$(shell cmd) | ` cmd ` |
-include .env | set dotenv-load |
ifeq ($(OS),Darwin) | if os() == "macos" { ... } |
ifndef VAR / $(or ...) | env_var_or_default("VAR", "default") |
make -C subdir | mod subdir |
$(MAKE) target | just target |
.DEFAULT_GOAL := help | First recipe is default |
@cmd (suppress echo) | @cmd (same) |
| Tab indentation | Any whitespace |
%: pattern rules | Not applicable — just has no file targets |
Variables:
VERSION := "1.0.0"
GIT_HASH := `git rev-parse --short HEAD`
DB_URL := env_var_or_default("DATABASE_URL", "postgres://localhost/dev")
OPEN := if os() == "macos" { "open" } else { "xdg-open" }Recipe attributes:
[confirm("Deploy to production?")]
deploy: build test
[macos]
open-docs:
open target/doc/index.html
[linux]
open-docs:
xdg-open target/doc/index.html
[private]
_setup:
mkdir -p tmp/
# Group recipes in `just --list` output
[group("checks")]
lint:
cargo clippy
[group("checks")]
fmt:
cargo fmt
# Multiple attributes — same line (comma-separated) or stacked
[group("dev"), no-cd]
status:
git statusRecipe argument flags (v1.46+): Use [arg()] to expose a parameter as a CLI option instead of a positional arg. Best for recipes a human runs at the prompt:
[arg("target", long, help="Build target architecture")]
[arg("release", long, value="true", help="Build in release mode")]
build target release="false":
cargo build --target {{target}} {{ if release == "true" { "--release" } else { "" } }}Usage: just build --target x86_64 --release. Run just --usage build to see the generated help.
Script blocks (`[script]`): Cleaner than shebang recipes and preferred when the body has shell control flow you don't want re-evaluated line-by-line (stable since just 1.44 — no set unstable needed):
[script("bash")]
deploy env:
set -e
case {{env}} in
prod) URL="https://prod.example.com" ;;
stage) URL="https://stage.example.com" ;;
*) echo "unknown env: {{env}}"; exit 1 ;;
esac
curl -X POST "$URL/deploy"
[script("python3")]
analyze:
import json
print(json.load(open("results.json"))["total"])[script()] is preferred over #!/usr/bin/env shebangs for cross-platform portability and so the whole body runs as one block.
Built-in color constants: Available globally without definition — RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, plus BOLD, UNDERLINE, and NORMAL to reset. Useful for status output in multi-step recipes:
@check:
cargo check
echo -e '{{ GREEN }}✓ check passed{{ NORMAL }}'
@fail-loudly:
echo -e '{{ BOLD + RED }}deploy aborted{{ NORMAL }}'Don't sprinkle color through every recipe — reserve it for status lines that summarize an aggregate step.
Watch / dev loop: just has no built-in watch flag — the manual's pattern is to let watchexec re-run a recipe whenever a watched file changes: watchexec just <recipe>. This is the inner-loop counterpart to the gate. Wrap the common case in a dev recipe and declare the dependency:
# https://github.com/watchexec/watchexec
watchexec := require("watchexec")
# Re-run the fast checks on every save (Ctrl-C to stop).
dev:
watchexec just testwatchexec just test is the day-to-day inner loop; the canonical build gate stays the after-each-change verification. See Re-running recipes when files change.
Modules (monorepo): See references/composition.md for the full pattern — aggregating module gates into one root build, and mod vs import.
mod api # looks for api/justfile or api.just
mod web
mod? local # optional — no error if missing
# Usage: just api::test, just web::buildShebang recipes (multi-line scripts, no `set unstable` needed):
analyze:
#!/usr/bin/env python3
import json
data = json.load(open("results.json"))
print(f"Total: {len(data)}")Prefer [script("python3")] over shebangs on just ≥ 1.44 (stable, no set unstable needed) — it runs the body as one block and avoids shebang-portability quirks.
set positional-arguments unless you have a strong reason — {{arg}} is clearermod) to split by concernbuild/ci gatebuild — that name is the gate; use dist or releaseredirect the existing CI verify step at just ci (see step 5)
just binary may not be on PATH — check with which just before generating recipes#!/usr/bin/env for portability across systemsdotenv-load exposes all env vars to all recipes — avoid for secrets-heavy projectsset positional-arguments changes how $1 works inside recipes — document when usedrequire("tool") validates at evaluate time — use {{ jq }} toinvoke via resolved path, or call jq directly; both work once require has confirmed the tool exists
[script()] is stable since just 1.44, modules since 1.31, [arg()] since1.46 — none need set unstable on a current just. Add set unstable only for an older just, and pin the relevant minimum in your README if you rely on them
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.