Agent Skills: ABOUTME: Make skill for idiomatic, maintainable Makefiles with modular patterns

>-

UncategorizedID: mauromedda/agent-toolkit/make

Install this agent skill to your local

pnpm dlx add-skill https://github.com/mauromedda/agent-toolkit/tree/HEAD/skills/make

Skill Files

Browse the full folder contents for make.

Download Skill

Loading file tree…

skills/make/SKILL.md

Skill Metadata

Name
make
Description
>-

ABOUTME: Make skill for idiomatic, maintainable Makefiles with modular patterns

ABOUTME: Emphasizes safety, self-documentation, modularity, and orchestration focus

Make Skill

Quick Reference

| Rule | Enforcement | |------|-------------| | Safety First | Always set SHELL, .SHELLFLAGS, .DELETE_ON_ERROR | | Self-Documenting | Every target has ## comment; use ##@ for groups | | .PHONY | Declare ALL non-file targets as phony | | Explicit | No magic; clear variable names and dependencies | | Modular | Split into *.mk files; use include | | Orchestration | Make orchestrates; scripts do complex logic | | Naming | Hyphen-case; verb-noun prefixes (e.g., stack-up) | | Help Default | .DEFAULT_GOAL := help |

πŸ›‘ FILE OPERATION CHECKPOINT (BLOCKING)

Before EVERY Write or Edit tool call on a Makefile or *.mk file:

╔══════════════════════════════════════════════════════════════════╗
β•‘  πŸ›‘ STOP - MAKE SKILL CHECK                                      β•‘
β•‘                                                                  β•‘
β•‘  You are about to modify a Makefile.                             β•‘
β•‘                                                                  β•‘
β•‘  QUESTION: Is /make skill currently active?                      β•‘
β•‘                                                                  β•‘
β•‘  If YES β†’ Proceed with the edit                                  β•‘
β•‘  If NO  β†’ STOP! Invoke /make FIRST, then edit                    β•‘
β•‘                                                                  β•‘
β•‘  This check applies to:                                          β•‘
β•‘  βœ— Write tool with file_path containing "Makefile"               β•‘
β•‘  βœ— Edit tool with file_path containing "Makefile"                β•‘
β•‘  βœ— Write/Edit with file_path ending in .mk                       β•‘
β•‘  βœ— ANY Makefile, regardless of conversation topic                β•‘
β•‘                                                                  β•‘
β•‘  Examples that REQUIRE this skill:                               β•‘
β•‘  - "add a build target" (edits Makefile)                         β•‘
β•‘  - "update the docker targets" (edits make/docker.mk)            β•‘
β•‘  - "fix the help target" (edits any Makefile)                    β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Why this matters: Makefiles without safety headers can fail silently or produce corrupt builds. The skill ensures .DELETE_ON_ERROR and proper .PHONY.

πŸ”„ RESUMED SESSION CHECKPOINT

When a session is resumed from context compaction, verify Makefile development state:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  SESSION RESUMED - MAKE SKILL VERIFICATION                  β”‚
β”‚                                                             β”‚
β”‚  Before continuing Makefile implementation:                 β”‚
β”‚                                                             β”‚
β”‚  1. Was I in the middle of writing Makefiles?               β”‚
β”‚     β†’ Check summary for "Makefile", "make target", ".mk"    β”‚
β”‚                                                             β”‚
β”‚  2. Did I follow all Make skill guidelines?                 β”‚
β”‚     β†’ Safety headers (SHELL, .SHELLFLAGS, .DELETE_ON_ERROR) β”‚
β”‚     β†’ .PHONY declarations for non-file targets              β”‚
β”‚     β†’ Self-documenting help target                          β”‚
β”‚     β†’ ABOUTME headers on new files                          β”‚
β”‚                                                             β”‚
β”‚  3. Check Makefile quality before continuing:               β”‚
β”‚     β†’ Run: make -n <target> (dry run)                       β”‚
β”‚     β†’ Verify help target works: make help                   β”‚
β”‚                                                             β”‚
β”‚  If implementation was in progress:                         β”‚
β”‚  β†’ Review the partial Makefile for completeness             β”‚
β”‚  β†’ Ensure safety headers are present                        β”‚
β”‚  β†’ Verify no recipes exceed 5 lines (move to scripts)       β”‚
β”‚  β†’ Re-invoke /make if skill context was lost                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

When to Use Make

Use Make for orchestration and build tasks:

  • Build automation (compile, link, bundle)
  • Development workflow commands (start, stop, test, lint)
  • Multi-service orchestration (Docker, Terraform)
  • CI/CD pipeline steps
  • Task runners with dependencies

Do NOT use Make for:

  • Complex business logic (use Python, Go, etc.)
  • Scripts requiring conditionals/loops (use Bash scripts)
  • Anything requiring error recovery
  • Configuration management (use dedicated tools)

Size guideline: If a Makefile exceeds ~300 lines, split into modular *.mk files.

Recipe rule: If a recipe exceeds 5 lines or contains if/else, move it to a script and invoke the /bash skill.

Core Principles

1. Safety Headers

Every Makefile starts with strict settings (like Bash's set -euo pipefail):

SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules

| Setting | Purpose | |---------|---------| | SHELL := bash | Use Bash instead of /bin/sh | | .SHELLFLAGS | -e exit on error, -u error on undefined, -o pipefail | | .DELETE_ON_ERROR | Remove target if recipe fails (prevents corrupt state) | | --warn-undefined-variables | Catch typos in variable names | | --no-builtin-rules | Disable implicit rules for clarity |

2. Self-Documenting Help System

Every Makefile must have a help target as default:

.DEFAULT_GOAL := help

##@ General
.PHONY: help
help: ## Display this help
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\033[0m\n"} \
		/^[a-zA-Z_0-9-]+:.*?##/ { printf "  \033[36m%-20s\033[0m %s\n", $$1, $$2 } \
		/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

Documentation syntax:

##@ Section Header        # Creates bold section in help output
target: ## Description    # Documents the target

3. Phony Targets

Declare ALL non-file-producing targets:

.PHONY: help build test clean deploy
.PHONY: docker-up docker-down
.PHONY: logs-%  # Pattern rules too

Why: Prevents conflicts with files named build, test, etc.

4. Explicit Over Implicit

  • Use $(VARIABLE) not $VARIABLE
  • Quote paths: "$(PATH_VAR)"
  • Name variables clearly: DOCKER_COMPOSE_FILES not DCF
  • Avoid automatic variables in complex contexts

5. Modular Design

Split large Makefiles into focused modules:

# Root Makefile
include make/common.mk
include make/docker.mk
include make/test.mk

# Optional includes (won't fail if missing)
-include make/local.mk

See resources/common.mk for a reusable library pattern.

6. Orchestration Focus

Make orchestrates; it doesn't implement:

# GOOD: Make orchestrates
test: ## Run tests
	@./scripts/run-tests.sh

# BAD: Complex logic in Make
test:
	@if [ -f .env ]; then \
		source .env && \
		for dir in $(TEST_DIRS); do \
			cd $$dir && npm test || exit 1; \
		done \
	fi

7. Consistent Naming

Prefixes for semantic grouping:

| Prefix | Purpose | Example | |--------|---------|---------| | stack- | Full application stack | stack-up, stack-down | | infra- | Infrastructure only | infra-network, infra-wait | | docker- | Docker operations | docker-build, docker-ps | | test- | Test execution | test-unit, test-e2e | | db- or migrate- | Database operations | db-migrate, db-seed |

Verbs:

| Verb | Meaning | |------|---------| | up / start | Create and run | | down / stop | Stop and remove | | restart | Stop then start | | build | Build only | | rebuild | Build and start | | logs | Stream logs | | status | Show current state |

Standard Template

Use resources/template.mk as a starter:

cp ~/.claude/skills/make/resources/template.mk ./Makefile

The template includes:

  • Safety headers
  • Color definitions
  • Logging macros
  • Help system
  • Example targets

Variable Patterns

Path Resolution (Portable)

# Get directory containing this Makefile
MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))

# Project root (if Makefile is in project root)
PROJECT_ROOT := $(MAKEFILE_DIR)

# Relative paths from Makefile location
SRC_DIR := $(PROJECT_ROOT)src
BUILD_DIR := $(PROJECT_ROOT)build

Default Values

# Use ?= for overridable defaults
COMPOSE_PROJECT_NAME ?= myproject
PORT ?= 8080

# Use := for computed values
TIMESTAMP := $(shell date +%Y%m%d-%H%M%S)
GIT_SHA := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")

Environment Exports

# Export for child processes
export DOCKER_BUILDKIT := 1
export COMPOSE_DOCKER_CLI_BUILD := 1

# Pass through from environment
export API_KEY
export DATABASE_URL

Conditional Variables

# Makefile conditionals (evaluated at parse time)
ifeq ($(CI),true)
    VERBOSE := 1
endif

ifdef DEBUG
    BUILD_FLAGS += -v
endif

ifndef REQUIRED_VAR
    $(error REQUIRED_VAR is not set)
endif

Macros (define/endef)

Logging Macros

# Color codes
CYAN := \033[0;36m
GREEN := \033[0;32m
YELLOW := \033[1;33m
RED := \033[0;31m
BOLD := \033[1m
NC := \033[0m

define log_info
	@printf "$(CYAN)[INFO]$(NC) %s\n" "$(1)"
endef

define log_success
	@printf "$(GREEN)[OK]$(NC) %s\n" "$(1)"
endef

define log_warn
	@printf "$(YELLOW)[WARN]$(NC) %s\n" "$(1)"
endef

define log_error
	@printf "$(RED)[ERROR]$(NC) %s\n" "$(1)"
endef

define log_step
	@printf "$(BOLD)>>> %s$(NC)\n" "$(1)"
endef

# Usage
build:
	$(call log_step,Building project)
	@go build ./...
	$(call log_success,Build completed)

Utility Macros

# Check if command exists
define check_cmd
	@command -v $(1) >/dev/null 2>&1 || { \
		printf "$(RED)[ERROR]$(NC) $(1) is required but not installed\n"; \
		exit 1; \
	}
endef

# Ensure directory exists
define ensure_dir
	@mkdir -p $(1)
endef

# Confirm dangerous action
define confirm
	@read -p "Are you sure? [y/N] " ans && [ "$${ans:-N}" = "y" ]
endef

# Usage
deploy: ## Deploy to production
	$(call check_cmd,kubectl)
	$(call confirm)
	$(call log_step,Deploying to production)
	@kubectl apply -f manifests/

Pattern Rules

Dynamic Targets with %

# logs-<service> - tail logs for any service
.PHONY: logs-%
logs-%: ## Tail logs for a specific service
	docker compose logs -f $*

# docker-exec-<service> - exec into any container
.PHONY: exec-%
exec-%: ## Execute shell in a container
	docker compose exec $* sh

# build-<component> - build specific component
.PHONY: build-%
build-%:
	$(call log_step,Building $*)
	@cd $* && make build

Automatic variables:

| Variable | Meaning | |----------|---------| | $@ | Target name | | $< | First prerequisite | | $^ | All prerequisites | | $* | Stem matched by % |

No-op Targets for Arguments

# Allow: make logs backend
# These are filter arguments, not real targets
.PHONY: backend frontend api worker
backend frontend api worker:
	@:

.PHONY: logs
logs: ## Tail logs (usage: make logs <service>)
	@component="$(filter-out $@,$(MAKECMDGOALS))"; \
	if [ -n "$$component" ]; then \
		docker compose logs -f $$component; \
	else \
		docker compose logs -f; \
	fi

Modular Library Pattern

Structure

project/
β”œβ”€β”€ Makefile              # Root: includes modules, defines high-level targets
└── make/
    β”œβ”€β”€ common.mk         # Shared: variables, colors, logging macros
    β”œβ”€β”€ docker.mk         # Docker Compose operations
    β”œβ”€β”€ test.mk           # Test orchestration
    └── app-python.mk     # Language-specific targets

Root Makefile

# Include order matters: common first, then specific
include make/common.mk
include make/docker.mk
include make/test.mk

# Optional local overrides
-include make/local.mk

##@ Stack Management
.PHONY: up
up: docker-up ## Start all services
	$(call log_success,Stack is running)

.PHONY: down
down: docker-down ## Stop all services
	$(call log_success,Stack stopped)

Consumer Pattern (Multi-Repo)

For projects that consume a shared Makefile library:

# Define path to shared infra
INFRA_DIR ?= $(HOME)/projects/shared-infra

# Clone if missing
$(if $(wildcard $(INFRA_DIR)),,$(shell git clone git@github.com:org/shared-infra.git $(INFRA_DIR)))

# Include shared modules
-include $(INFRA_DIR)/make/common.mk
-include $(INFRA_DIR)/make/app.mk
-include $(INFRA_DIR)/make/app-python.mk

See resources/common.mk for a complete reusable library.

Dependency Management

Prerequisite Chains

# Direct dependencies
deploy: build test ## Deploy (requires build and test)
	@./scripts/deploy.sh

# Chain dependencies
clean-all: clean-build clean-test clean-docker

# Order-only prerequisites (directory must exist, but changes don't trigger rebuild)
$(BUILD_DIR)/output: source.c | $(BUILD_DIR)
	gcc -o $@ $<

$(BUILD_DIR):
	mkdir -p $@

Conditional Dependencies

.PHONY: start
start: ## Start services (optionally with migrations)
ifeq ($(MIGRATE),1)
	$(call log_info,Running migrations first)
	@$(MAKE) db-migrate
endif
	@docker compose up -d

Sentinel Files (Complex Dependencies)

For dependencies that don't produce predictable files (like npm install):

# Sentinel file tracks when install was last run
.stamps:
	@mkdir -p .stamps

.stamps/npm-install: package.json package-lock.json | .stamps
	npm ci
	@touch $@

.stamps/pip-install: requirements.txt | .stamps
	pip install -r requirements.txt
	@touch $@

# Depend on sentinel, not directory
build: .stamps/npm-install
	npm run build

clean:
	rm -rf .stamps node_modules

Integration Patterns

Docker Compose

# Compose file configuration
COMPOSE_FILES := -f docker-compose.yml
ifdef CI
    COMPOSE_FILES += -f docker-compose.ci.yml
endif

COMPOSE := docker compose $(COMPOSE_FILES)

.PHONY: docker-up
docker-up: ## Start Docker services
	$(COMPOSE) up -d

.PHONY: docker-down
docker-down: ## Stop Docker services
	$(COMPOSE) down

Terraform

TF_DIR := terraform

.PHONY: tf-init
tf-init: ## Initialize Terraform
	cd $(TF_DIR) && terraform init

.PHONY: tf-plan
tf-plan: ## Plan Terraform changes
	cd $(TF_DIR) && terraform plan -out=tfplan

.PHONY: tf-apply
tf-apply: ## Apply Terraform changes
	$(call confirm)
	cd $(TF_DIR) && terraform apply tfplan

Anti-Patterns

| Anti-Pattern | Problem | Fix | |--------------|---------|-----| | Spaces instead of tabs | Recipes won't run | Use tabs for recipe lines | | Missing .PHONY | Target skipped if file exists | Declare all non-file targets | | Bare rm without -f | Fails if file missing | Use rm -f or rm -rf | | Ignoring errors silently | @command \|\| true hides failures | Handle errors explicitly | | Complex shell logic | Unmaintainable, error-prone | Move to script; invoke /bash | | Recursive make without $(MAKE) | Breaks parallelism, options | Use $(MAKE) -C subdir | | Hardcoded paths | Not portable | Use variables with defaults | | No help target | Users don't know available commands | Always provide help | | Excessive @ silencing | Can't debug issues | Only silence noise, not errors |

Validation

Run the validation script to check Makefile quality:

~/.claude/skills/make/scripts/validate_makefile.sh [Makefile]

The script checks:

  • Safety headers present
  • Help target exists
  • .PHONY declarations
  • Forbidden patterns (complex shell logic, missing error handling)
  • Uses checkmake if available

Checklist

Before committing a Makefile:

  • [ ] Safety headers present (SHELL, .SHELLFLAGS, .DELETE_ON_ERROR)
  • [ ] .DEFAULT_GOAL := help set
  • [ ] help target with awk-based documentation parser
  • [ ] All non-file targets declared .PHONY
  • [ ] Variables use $(VAR) syntax (not $VAR)
  • [ ] Paths are quoted where needed
  • [ ] No recipes exceed 5 lines (moved to scripts)
  • [ ] No complex shell logic (if/else, loops) in recipes
  • [ ] Logging macros used for user feedback
  • [ ] Dependencies declared correctly
  • [ ] Runs validate_makefile.sh without errors
  • [ ] Tested with make -n <target> (dry run)
ABOUTME: Make skill for idiomatic, maintainable Makefiles with modular patterns Skill | Agent Skills