| name | makefile |
| description | Create or improve Makefiles with minimal complexity. Templates available: base, python-uv, python-fastapi, nodejs, go, chrome-extension, flutter. |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash |
Makefile Helper
Create Makefiles that are simple, discoverable, and maintainable.
Core Principles
- Default to rich help - Use categorized help with emoji headers unless user requests minimal
- Ask about structure upfront - For new Makefiles, ask: "Flat or modular? Rich help or minimal?"
- Follow existing conventions - Match the project's style if Makefile already exists
- Don't over-engineer - Solve the immediate need, not hypothetical futures
- Use
uv run - Always run Python commands via uv run for venv context
- Explain decisions - If choosing flat/minimal, explain why before generating
When to Use This Skill
- Creating a new Makefile for a project
- Adding specific targets to an existing Makefile
- Improving/refactoring an existing Makefile
- Setting up CI/CD make targets
- Distributing pre-built binaries via GitHub Releases
Quick Start
For new projects, use the appropriate template:
| Project Type | Template | Complexity |
|---|
| Any project | templates/base.mk | Minimal |
| Python with uv | templates/python-uv.mk | Standard |
| Python FastAPI | templates/python-fastapi.mk | Full-featured |
| Node.js | templates/nodejs.mk | Standard |
| Go | templates/go.mk | Standard |
| Chrome Extension | templates/chrome-extension.mk | Modular |
| Flutter App | templates/flutter.mk | Modular |
Chrome Extension Structure
The chrome extension template uses a modular structure:
Makefile # Main file with help + includes
makefiles/
colors.mk # ANSI colors & print helpers
common.mk # Shell flags, VERBOSE mode, guards
build.mk # Build zip, version bump, releases
dev.mk # Test, lint, clean, install
Copy from templates/chrome-extension-modules/ to your project's makefiles/ directory.
Key features:
build-release - Version bump menu (major/minor/patch) + zip for Chrome Web Store
build-beta - (Optional) GitHub releases with gh CLI
dev-test / dev-test-e2e - Vitest + Playwright testing
VERBOSE=1 make <target> - Show commands for debugging
Flutter App Structure
Makefile # Main file with help + includes
makefiles/
colors.mk # ANSI colors & print helpers
common.mk # Shell flags, VERBOSE mode, guards
dev.mk # Setup, run simulator/device, devices, clean
build.mk # iOS/Android builds (IPA, APK, AAB)
deploy.mk # TestFlight upload
lint.mk # Dart analyze & format
Copy from templates/flutter-modules/ to your project's makefiles/ directory.
Key features:
flutter-run-ios auto-boots simulator and waits for it
flutter-run-android auto-launches emulator and waits for it
flutter-run-device auto-detects or uses FLUTTER_IOS_DEVICE / FLUTTER_ANDROID_DEVICE
flutter-build-ipa + flutter-deploy-testflight full iOS release workflow
flutter-lint FIX=true Dart formatting with FIX pattern
VERBOSE=1 make <target> show commands for debugging
Interaction Pattern
- Understand - What specific problem are we solving?
- Check existing - Is there already a Makefile? Read it first!
- Default to modular - For 5+ targets, use modular structure unless user requests flat
- Match preferences - Use python-fastapi.mk template style as default for rich help
- Explain structure - If you choose flat/minimal, explain the reasoning
- Iterate - Add complexity or simplify based on feedback
Naming Conventions
Use kebab-case with consistent prefix-based grouping:
build-release, build-zip, build-clean
dev-run, dev-test, dev-lint
db-start, db-stop, db-migrate
env-local, env-prod, env-show
_build-zip-internal, _prompt-version
run-dev, build, localEnv, test_net
build_release, dev_test
Name targets after the action, not the tool:
remove-bg
format-code
lint-check
rembg
prettier
eslint
Key Patterns
Binary Distribution
For projects distributed as pre-built binaries via GitHub Releases:
GITHUB_REPO ?= owner/repo
OS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
ARCH := $(shell uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')
.PHONY: install-cli
install-cli: ## Download and install CLI from latest GitHub release
@RELEASE=$$(curl -fsSL https://api.github.com/repos/$(GITHUB_REPO)/releases/latest | grep tag_name | cut -d'"' -f4); \
echo "Installing $$RELEASE for $(OS)/$(ARCH)..."; \
curl -fsSL -o ~/.local/bin/cli \
"https://github.com/$(GITHUB_REPO)/releases/download/$$RELEASE/cli-$(OS)-$(ARCH)"; \
chmod +x ~/.local/bin/cli
Key considerations:
- Detect OS and architecture automatically
- Download from GitHub Releases (no Python/uv required)
- Install to
~/.local/bin (user-writable, in PATH)
- Preserve existing config files during updates
Always Use uv run for Python
dev-check:
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/
uv run mypy src/
dev-format:
uv run ruff check --fix src/ tests/
uv run ruff format src/ tests/
dev-format:
ruff format .
Use uv sync (not pip install)
env-install:
uv sync
Categorized Help (for 5+ targets)
help:
@printf "$(BOLD)=== 🚀 API ===$(RESET)\n"
@printf "$(CYAN)%-25s$(RESET) %s\n" "api-run" "Start server"
@printf "%-25s $(GREEN)make api-run [--reload]$(RESET)\n" ""
Makefile ordering rule - help targets go LAST, just before catch-all:
- Configuration (
?= variables)
HELP_PATTERNS definition
- Imports (
include ./makefiles/*.mk)
- Main targets (grouped by function)
help: and help-unclassified: targets
- Catch-all
%: rule (absolute last)
Preflight Checks
_check-docker:
@docker info >/dev/null 2>&1 || { echo "Docker not running"; exit 1; }
db-start: _check-docker # Runs check first
docker compose up -d
External Tool Dependencies
When a target requires an external tool (not a system service):
- Don't create public install targets (no
make install-foo)
- Use internal check as dependency (prefix with
_, no ## comment)
- Show install command on failure - tell user what to run, don't do it for them
_check-rembg:
@command -v rembg >/dev/null 2>&1 || { \
printf "$(RED)$(CROSS) rembg not installed$(RESET)\n"; \
printf "$(YELLOW)Run: uv tool install \"rembg[cli]\"$(RESET)\n"; \
exit 1; \
}
.PHONY: remove-bg
remove-bg: _check-rembg ## Remove background from image
rembg i "$(IN)" "$(OUT)"
Key points:
- Name target after the action (
remove-bg), not the tool (rembg)
- Check runs automatically - user just runs
make remove-bg
- If tool missing, user sees exactly what command to run
Env File Loading
Load .env and export to child processes:
-include .env
.EXPORT_ALL_VARIABLES:
For per-target env override:
test-e2e:
@set -a && . "$${E2E_ENV:-.env}" && set +a && uv run pytest tests/e2e/
FIX Variable for Check/Format Targets
Use a FIX variable to toggle between check-only and auto-fix modes:
FIX ?= false
dev-check: ## Run linting and type checks (FIX=false: check only)
$(call print_section,Running checks)
ifeq ($(FIX),true)
uv run ruff check --fix src/ tests/
uv run ruff format src/ tests/
else
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/
endif
uv run mypy src/
$(call print_success,All checks passed)
In help output, show usage:
@printf "$(CYAN)%-25s$(RESET) %s\n" "dev-check" "Run linting (FIX=false: check only)"
@printf "%-25s $(GREEN)make dev-check FIX=true$(RESET) <- auto-fix issues\n" ""
When to Modularize
Default to modular for any new Makefile with 5+ targets.
Use flat file only when:
- Simple scripts or single-purpose tools
- User explicitly requests it
- < 5 targets with no expected growth
Standard modular structure:
Makefile # Config, imports, help, catch-all
makefiles/
colors.mk # ANSI colors & print helpers
common.mk # Shell flags, VERBOSE, guards
<domain>.mk # Actual targets (build.mk, dev.mk, etc.)
Legacy Compatibility
Default: NO legacy aliases. Only add when:
- User explicitly requests backwards compatibility
- Existing CI/scripts depend on old names (verify with
rg "make old-name")
When legacy IS needed, put them in a clearly marked section AFTER main targets but BEFORE help:
.PHONY: old-name
old-name: new_name ## (Legacy) Description
Key Rules
- Always read existing Makefile before changes
- Search codebase before renaming targets (
rg "make old-target")
- Test with
make help and make -n target
- Update docs after Makefile changes - When adding new targets:
- Add to
make help output (in the appropriate section)
- Update
CLAUDE.md if the project has one (document new targets)
- Update any other relevant docs (README.md, Agents.md, etc.)
- Never add targets without clear purpose
- No line-specific references - Avoid patterns like "Makefile:44" in docs/comments; use target names instead
- Single source of truth - Config vars defined once in root Makefile, not duplicated in modules
- Help coverage audit - All targets with
## must appear in either make help or make help-unclassified
Help System
ASCII box title for visibility:
help:
@printf "\n"
@printf "$(BOLD)$(CYAN)╔═══════════════════════════╗$(RESET)\n"
@printf "$(BOLD)$(CYAN)║ Project Name ║$(RESET)\n"
@printf "$(BOLD)$(CYAN)╚═══════════════════════════╝$(RESET)\n\n"
Categorized help with sections:
@printf "$(BOLD)=== 🏗️ Build ===$(RESET)\n"
@grep -h -E '^build-[a-zA-Z_-]+:.*?
@printf "$(BOLD)=== 🔧 Development ===$(RESET)\n"
@grep -h -E '^dev-[a-zA-Z_-]+:.*?
Key help patterns:
help - Main categorized help
help-unclassified - Show targets not in any category (useful for auditing)
help-all - Show everything including internal targets
- Hidden targets: prefix with
_ (e.g., _build-internal)
- Legacy targets: label with
## (Legacy) and filter from main help
Always include a Help section in make help output:
@printf "$(BOLD)=== ❓ Help ===$(RESET)\n"
@printf "$(CYAN)%-25s$(RESET) %s\n" "help" "Show this help"
@printf "$(CYAN)%-25s$(RESET) %s\n" "help-unclassified" "Show targets not in categorized help"
@printf "\n"
help-unclassified pattern (note the sed to strip filename prefix):
help-unclassified: ## Show targets not in categorized help
@printf "$(BOLD)Targets not in main help:$(RESET)\n"
@grep -E '^[a-zA-Z_-]+:.*?
sed 's/^[^:]*://' | \
grep -v -E '^(env-|dev-|clean|help)' | \
awk 'BEGIN {FS = ":.*?## "}; {printf "$(CYAN)%-25s$(RESET) %s\n", $$1, $$2}' || \
printf " (none)\n"
Description format - one line with example:
@printf "$(CYAN)%-14s$(RESET) %s\n" "scrape" "Fetch posts into SQLite, detect problems"
@printf " $(GREEN)make scrape SUBREDDITS=python,django LIMIT=10$(RESET)\n"
@printf "$(CYAN)%-14s$(RESET) %s\n" "dev-check" "Run ruff linter and formatter"
@printf " $(GREEN)make dev-check FIX=true$(RESET)\n"
@printf " $(CYAN)$(BOLD)setup$(RESET)\n"
@printf " Install Python dependencies using uv. Run this once after cloning.\n"
@printf " Creates .venv/ and installs packages from pyproject.toml.\n"
@printf " $(GREEN)make setup$(RESET)\n"
Help description rules:
- One line max - Description must fit on single line unless user explicitly asks for more
- Include what it affects - e.g., "creates .venv", "exports to CSV", "deletes database"
- Example on next line - Show realistic usage with parameters in
$(GREEN)
- Skip examples for simple targets - If no parameters, no example needed
Catch-all redirects to help:
%:
@printf "$(RED)Unknown target '$@'$(RESET)\n"
@$(MAKE) help
Common Pitfalls
| Issue | Fix |
|---|
$var in shell loops | Use $$var to escape for make |
Catch-all %: shows error | Redirect to @$(MAKE) help instead |
| Config vars scattered | Put all ?= overridable defaults at TOP of root Makefile |
HELP_PATTERNS mismatch | Must match grep patterns in help target exactly |
| Duplicate defs in modules | Define once in root, reference in modules |
| Trailing whitespace in vars | Causes path splitting bugs - trim all variable definitions |
.PHONY on file targets | Only use .PHONY for non-file targets |
| Too many public targets | Don't expose install-X or check-X - use internal _check-X dependencies |
$(DIM) for usage text | Appears grey/unreadable - use $(GREEN) instead |
| Target named after tool | Name after the action: remove-bg not rembg |
help-unclassified shows filename | Use sed 's/^[^:]*://' to strip Makefile: prefix |
No .env export | Add -include .env and .EXPORT_ALL_VARIABLES: at top |
Cleanup Makefile Workflow
When user says "cleanup my makefiles":
IMPORTANT: Build a plan first and explain it to the user before implementing anything.
Phase 1: Audit (no changes yet)
make help
make help-unclassified
cat Makefile
ls makefiles/*.mk 2>/dev/null
rg "make " --type md
grep -E '\s+$' Makefile makefiles/*.mk
Phase 2: Build & Present Plan
Create a checklist of proposed changes:
Ask user to approve the plan before proceeding.
Phase 3: Implement (after approval)
- Restructure (if needed) - Create
makefiles/ directory, split into modules
- Remove legacy - Delete approved targets
- Consolidate duplicates - Merge into single targets
- Rename targets - Apply hyphen convention, add
_ prefix for internal
- Rewrite descriptions - Make each
## explain the purpose
- Fix formatting
- Usage examples in yellow:
$(YELLOW)make foo$(RESET)
- Remove trailing whitespace
.PHONY only on non-file targets
- Add missing pieces -
help-unclassified, catch-all %:, etc.
Phase 4: Verify
make help
make help-unclassified
make -n <target>
What NOT to do without asking:
- Rename targets that CI/scripts depend on
- Remove targets that look unused
- Change structure (flat ↔ modular) without approval
Files in This Skill
reference.md - Detailed patterns, categorized help, error handling
templates/ - Full copy-paste Makefiles for each stack
modules/ - Reusable pieces for complex projects
Example: Adding a Target
User: "Add a target to run my tests"
.PHONY: test
test: ## Run tests
$(call print_section,Running tests)
uv run pytest tests/ -v
$(call print_success,Tests passed)
User: "Add database targets"
.PHONY: db-start db-stop db-migrate
db-start: _check-docker ## Start database
docker compose up -d postgres
db-stop: ## Stop database
docker compose down
db-migrate: _check-postgres ## Run migrations
uv run alembic upgrade head