Claude
Skills
Sign in
Back

just-pro

Included with Lifetime
$97 forever

Patterns for setting up just (command runner) in projects. Use when creating build systems, setting up new repos, organizing build/test/lint recipes, or when the user asks about just/justfile configuration. Covers both simple single-project repos and monorepos with hierarchical justfile modules, mise integration, and conventional recipe naming (check, build, test, lint, fmt).

Code Review

What this skill does


# Justfile Skill

Build system configuration using [just](https://just.systems), a modern command runner.

**Related skills:**
- **mise** - Tool version management (includes just+mise integration patterns)
- **go-pro**, **rust-pro**, **typescript-pro** - Language-specific templates

## Installation

```bash
# Via mise (recommended - version pinned per-project)
mise use just

# macOS
brew install just

# Linux/Windows (via cargo)
cargo install just

# Or prebuilt binaries: https://github.com/casey/just/releases
```

## When to Use Just

| Scenario | Recommendation |
|----------|----------------|
| Cross-language monorepo | **just** - Unified interface across packages |
| Single Go/Rust project | **just** or language-native (go/cargo) |
| Node.js project | npm scripts primary, just optional wrapper |
| CI/CD porcelain | **just** - Single entry point for all operations |
| Simple scripts | **just** - Better than shell scripts |

## Project Patterns

### Simple Repo (Single Package)

For single-language projects, create one `justfile` at repo root.

```just
# Project Build System
# Usage: just --list

default:
    @just --list

# === Quality Gates ===
check: fmt lint test
    @echo "All checks passed"

fmt:
    go fmt ./...

lint:
    go tool golangci-lint run

test:
    go test -race ./...

build:
    go build -o bin/app ./cmd/app
```

See `references/simple-repo.just` for complete templates.

### Monorepo (Multiple Packages)

For monorepos, use hierarchical justfiles with the `mod` system:

```
repo/
├── justfile              # Router - imports package modules
└── packages/
    ├── api-go/
    │   └── justfile      # Go package recipes
    ├── web/
    │   └── justfile      # TypeScript package recipes (optional)
    └── ops/
        └── justfile      # DevOps recipes
```

**Root justfile** (router):
```just
# Monorepo Build System
# Usage: just --list
# Usage: just go <recipe>

mod go "packages/api-go"
mod web "packages/web"
mod ops "ops"

default:
    @just --list

# Umbrella recipes call into modules
check: (go::check) (web::check)
    @echo "All checks passed"

setup: (go::setup) (web::setup)
    @echo "All toolchains ready"
```

**Commands become**: `just go check`, `just go lint`, `just web build`, `just ops deploy`

See `references/monorepo-root.just` and `references/package-go.just` for templates.

## Recipe Patterns

### Quality Gates

Always provide a single `check` recipe that runs all quality gates:

```just
# Full quality gates
check: fmt lint test coverage-check
    @echo "All checks passed"
```

In single-package repos, `just check` can run in a pre-commit hook. In monorepos, it's too slow for pre-commit — use lint-staged in the hook and run `just check` manually or in CI.

### Fast Check (Stop Hook Gate)

The dm-work Stop hook (`run-gates-on-stop.sh`) runs quality gates after every turn. It auto-detects: `just check-fast` → `just check` → `npm run check`, in that order. If `just check` is slow (>10s), add a `check-fast` recipe that drops the slowest steps — no additional hook configuration needed.

Profile first to find what's slow — usually production builds and coverage, not tests:

```just
# Full gate — pre-commit, CI
check: fmt lint test coverage-check build
    @echo "All checks passed"

# Fast gate — Stop hook, iterative dev (drop slow build + coverage)
check-fast: fmt lint test
    @echo "Fast checks passed"
```

**Tips:** Add `--cache` to ESLint for repeat-run speedup (~6s to ~1s). Add `.eslintcache` to `.gitignore`.

### Clean Recipe

Standard cleanup for build artifacts and caches:

```just
# Remove build artifacts and caches
clean:
    rm -rf build/ coverage.out node_modules/.cache
```

### Parallel Execution

`just` doesn't have native parallel deps. Use shell backgrounding for monorepos:

```just
# Parallel check across packages
check:
    #!/usr/bin/env bash
    set -uo pipefail
    just api check &  PID1=$!
    just web check &  PID2=$!
    just mcp check &  PID3=$!
    FAIL=0
    wait $PID1 || FAIL=1
    wait $PID2 || FAIL=1
    wait $PID3 || FAIL=1
    [ $FAIL -eq 0 ] || { echo "Checks failed"; exit 1; }
    echo "All checks passed"
```

Note: use `set -uo pipefail` (not `-euo`) — with `-e`, a failed `wait` exits before checking the others.

### Coverage Enforcement

Use shebang for multi-line shell logic:

```just
# Check coverage meets 70% minimum
coverage-check:
    #!/usr/bin/env bash
    set -euo pipefail
    go test -race -coverprofile=coverage.out ./...
    COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
    COVERAGE_INT=${COVERAGE%.*}
    [ "$COVERAGE_INT" -ge 70 ] || (echo "FAIL: Coverage ${COVERAGE}% < 70%" && exit 1)
    echo "PASS: Coverage ${COVERAGE}%"
```

The `#!/usr/bin/env bash` shebang runs the entire recipe as a single shell script (variables persist across lines).

### Umbrella Recipes with Module Dependencies

```just
# Root justfile calling into modules
check: (go::check) (web::check)
    @echo "All checks passed"
```

### Optional Modules

For packages that may not exist in all environments:

```just
mod? optional-package "packages/optional"
```

### Documentation Comments

Every recipe should have a doc comment (shown in `just --list`):

```just
# Run unit tests with race detection
test:
    go test -race ./...
```

**Module doc comments** - Comments above `mod` statements also appear in `just --list`:

```just
# Go API (GraphQL + Watermill)
mod api "packages/api"

# Next.js frontend
mod web "packages/web"

# PostgreSQL database
mod db "packages/db"
```

Output of `just --list`:
```
api ...      # Go API (GraphQL + Watermill)
db ...       # PostgreSQL database
web ...      # Next.js frontend
```

Always add doc comments to module imports for discoverability.

## Module System Details

### Syntax

```just
mod <name> "<path>"              # Required module
mod? <name> "<path>"             # Optional module (no error if missing)
mod <name>                       # Module at ./<name>/justfile
```

### Working Directory

Recipes in modules run with their working directory set to the module's directory, not the root. This is the desired behavior for package-local commands.

### Listing Module Recipes

```bash
just --list           # Root recipes + module names
just --list go        # Recipes in 'go' module
just --list-submodules # All recipes including submodules
```

**Important**: Use `just --list <module>` not `just <module> --list`. The flag must come before the module name.

### Calling Module Recipes

```bash
just go check         # From command line
(go::check)          # As dependency in justfile
```

## Integration with Language Toolchains

### Go Projects

```just
# Uses go.mod tool directive for pinned versions
lint:
    go tool golangci-lint run

# Auto-fix linting issues (chains goimports to fix imports after modernize changes)
fix:
    go tool golangci-lint run --fix
    go tool goimports -w .
```

**Why chain goimports?** The `modernize` linter may change code (e.g., `fmt.Errorf()` → `errors.New()`) without updating imports, breaking builds. Running `goimports` after `--fix` resolves this.

### TypeScript/Node Projects

Always export `node_modules/.bin` onto PATH so recipes can call tools directly without `npx` (slow) or `npm run` (loses justfile's value as a unified interface):

```just
export PATH := "./node_modules/.bin:" + env_var("PATH")
```

With this, recipes call `tsc`, `eslint`, `vitest` etc. directly. Without it, every recipe either needs `npx` (adds startup overhead) or delegates to `npm run` (making just a thin pass-through).

Two approaches:

**Option A**: Direct invocation (recommended — self-contained recipes)
```just
export PATH := "./node_modules/.bin:" + env_var("PATH")

check: typecheck lint test
    @echo "All checks passed"

typecheck:
    tsc --noEmit

lint:
    eslint .

test:
    vitest run
```

**Option B**: Thin wrapper (simpler, delegates to package.json scripts)
```just
# packages/web/justfile
check:
    npm run ch

Related in Code Review