Best practices for creating self-documenting Makefiles with auto-generated help. Use when creating or modifying Makefiles to ensure they follow conventions for discoverability and maintainability.
Creates self-documenting Makefiles with auto-generated help from `##` comments. Use when creating or modifying Makefiles to ensure discoverability and maintainability.
/plugin marketplace add plinde/claude-plugins/plugin install makefile-best-practices@plinde-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
When creating or modifying Makefiles, follow these principles to ensure they are self-documenting and user-friendly.
helpAlways set help as the default target so users can discover available commands:
.DEFAULT_GOAL := help
This ensures running make without arguments shows available targets instead of executing an arbitrary first target.
The help target should automatically build itself from comments in the Makefile. Use the ## comment pattern:
target-name: ## Description of what this target does
@command here
help: ## Show this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
target: ## Description - The ## marks the description for that target##@ Section - Creates optional section headers in the help outputawk command parses these patterns and formats the outputWhen asked to audit a Makefile, check for the following issues and report findings:
.DEFAULT_GOAL := help is sethelp target existshelp target uses the self-documenting awk pattern## Description comments##).PHONY declarations for non-file targetshelp target is not using $(MAKEFILE_LIST) (won't work with includes)Makefile Audit: <path>
PASS: .DEFAULT_GOAL set to help
PASS: help target exists
FAIL: 3 targets missing ## descriptions: build, test, deploy
WARN: Missing .PHONY for: build, test, clean
Summary: 2 passed, 1 failed, 1 warning
Provide specific line numbers and suggested fixes for each issue found.
For one-off shell scripts or binaries that should be available in ~/bin, use this symlink pattern:
SCRIPT_NAME := my-script.sh
INSTALL_DIR := $(HOME)/bin
INSTALL_PATH := $(INSTALL_DIR)/$(SCRIPT_NAME)
SOURCE_PATH := $(CURDIR)/$(SCRIPT_NAME)
install: ## Install symlink to ~/bin
@mkdir -p $(INSTALL_DIR)
@if [ -L "$(INSTALL_PATH)" ]; then \
echo "Removing existing symlink..."; \
rm -f "$(INSTALL_PATH)"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Error: $(INSTALL_PATH) exists and is not a symlink"; \
exit 1; \
fi
@ln -s "$(SOURCE_PATH)" "$(INSTALL_PATH)"
@echo "Installed: $(INSTALL_PATH) -> $(SOURCE_PATH)"
uninstall: ## Remove symlink from ~/bin
@if [ -L "$(INSTALL_PATH)" ]; then \
rm -f "$(INSTALL_PATH)"; \
echo "Removed: $(INSTALL_PATH)"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Error: $(INSTALL_PATH) exists but is not a symlink (not removing)"; \
exit 1; \
else \
echo "Nothing to remove: $(INSTALL_PATH) does not exist"; \
fi
check: ## Check installation status
@echo "Source: $(SOURCE_PATH)"
@if [ -L "$(INSTALL_PATH)" ]; then \
echo "Status: Installed (symlink)"; \
echo "Target: $$(readlink "$(INSTALL_PATH)")"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Status: Exists but NOT a symlink"; \
else \
echo "Status: Not installed"; \
fi
mkdir -p ensures the directory existsIf the script needs to be sourced (not executed), add post-install instructions:
install: ## Install symlink to ~/bin
@mkdir -p $(INSTALL_DIR)
# ... symlink creation ...
@echo ""
@echo "Add to your shell config:"
@echo " source ~/bin/$(SCRIPT_NAME)"
# my-tool Makefile
SCRIPT_NAME := my-tool.sh
INSTALL_DIR := $(HOME)/bin
INSTALL_PATH := $(INSTALL_DIR)/$(SCRIPT_NAME)
SOURCE_PATH := $(CURDIR)/$(SCRIPT_NAME)
.PHONY: help install uninstall check lint test all clean
.DEFAULT_GOAL := help
##@ General
help: ## Show this help message
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
##@ Installation
install: ## Install symlink to ~/bin
@mkdir -p $(INSTALL_DIR)
@if [ -L "$(INSTALL_PATH)" ]; then \
echo "Removing existing symlink..."; \
rm -f "$(INSTALL_PATH)"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Error: $(INSTALL_PATH) exists and is not a symlink"; \
exit 1; \
fi
@ln -s "$(SOURCE_PATH)" "$(INSTALL_PATH)"
@echo "Installed: $(INSTALL_PATH) -> $(SOURCE_PATH)"
uninstall: ## Remove symlink from ~/bin
@if [ -L "$(INSTALL_PATH)" ]; then \
rm -f "$(INSTALL_PATH)"; \
echo "Removed: $(INSTALL_PATH)"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Error: $(INSTALL_PATH) exists but is not a symlink (not removing)"; \
exit 1; \
else \
echo "Nothing to remove: $(INSTALL_PATH) does not exist"; \
fi
check: ## Check installation status
@echo "Source: $(SOURCE_PATH)"
@if [ -L "$(INSTALL_PATH)" ]; then \
echo "Status: Installed (symlink)"; \
echo "Target: $$(readlink "$(INSTALL_PATH)")"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Status: Exists but NOT a symlink"; \
else \
echo "Status: Not installed"; \
fi
##@ Development
lint: ## Run shellcheck on scripts
shellcheck $(SCRIPT_NAME)
test: ## Run tests
./test.sh
# Stubs to satisfy checkmake minphony rule
all: help
clean: uninstall
Implement GDPR-compliant data handling with consent management, data subject rights, and privacy by design. Use when building systems that process EU personal data, implementing privacy controls, or conducting GDPR compliance reviews.
Create employment contracts, offer letters, and HR policy documents following legal best practices. Use when drafting employment agreements, creating HR policies, or standardizing employment documentation.