What I do
- •Define the standard Makefile target structure for local, container, and cloud operations
- •Guide modular shell script authoring in
scripts/ - •Document per-language build and run patterns
- •Explain the thin-wrapper pattern and why complex logic belongs in scripts
When to use me
Use this skill when scaffolding a new project's operational structure, when modifying Makefile targets or scripts, or when onboarding someone to the project's development workflow.
Makefile Conventions
Three Domains, Six Actions
| Domain | init | clean | build | run/deploy | test | lint |
|---|---|---|---|---|---|---|
| Local dev | local-init | local-clean | local-build | local-run | local-test | local-lint |
| Container dev | container-init | container-clean | container-build | container-run | — | — |
| Cloud runtime | cloud-init | cloud-clean | cloud-build | cloud-deploy | — | — |
Thin-wrapper Pattern
Every Makefile target is a one-liner that calls a script:
local-build: ## Build the project locally @bash scripts/local.sh build
Rules:
- •No complex logic in the Makefile -- it's only a dispatch layer
- •Use
@prefix to suppress command echo - •Add
## descriptioncomments formake helpsupport - •All
.PHONYtargets declared at the top
Help Target
Include a help target that extracts descriptions from ## comments:
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
Scripts Conventions
Directory Layout
scripts/ common.sh # Shared functions (always sourced first) local.sh # Local dev operations container.sh # Container operations (Podman) cloud.sh # Cloud operations (gcloud, Cloud Build)
common.sh Pattern
Every script sources common.sh first:
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
common.sh provides:
- •
set -euo pipefail-- fail on errors, undefined vars, pipe failures - •Colored logging:
log_info,log_ok,log_warn,log_error,die - •Environment loading from
.env(if it exists) - •Default variables:
PROJECT_NAME,IMAGE_NAME,IMAGE_TAG,GCP_PROJECT,GCP_REGION - •Helper functions:
require_cmd(assert CLI exists),confirm(yes/no prompt)
Subcommand Dispatch
Each script accepts a subcommand as its first argument:
case "${1:-}" in
init) init ;;
clean) clean ;;
build) build ;;
run) run ;;
*) die "Usage: $0 {init|clean|build|run}" ;;
esac
Script Permissions
All scripts must be executable: chmod +x scripts/*.sh
Container Files
Container build files live in cicd/, NOT at the project root:
cicd/ Dockerfile # Multi-stage build, tailored to project type .dockerignore # Build context exclusions
The scripts/container.sh build command references cicd/Dockerfile:
podman build -f cicd/Dockerfile -t "${IMAGE_NAME}:${IMAGE_TAG}" .
The build context is the project root (.) so source code is accessible,
but the Dockerfile itself is kept separate in cicd/.
.dockerignore
Must exclude:
- •
.git,.gitignore,*.md,LICENSE - •
.env,.env.*(secrets must not be baked into images) - •
cicd/terraform/,cicd/cloudbuild*.yaml(not needed in the image) - •Language-specific artifacts (
node_modules/,__pycache__/,target/, etc.)
Per-language Patterns
Node.js/TypeScript
- •init:
npm install - •build:
npm run build - •run:
npm run dev - •test:
npm test - •lint:
npm run lintornpx eslint . - •Dockerfile: Multi-stage with
node:20-alpine,npm ciin builder
Go
- •init:
go mod download - •build:
go build -o bin/ ./... - •run:
go run . - •test:
go test ./... - •lint:
golangci-lint runorgo vet ./... - •Dockerfile: Multi-stage with
golang:1.22-alpine,distrolessruntime
Python
- •init:
python3 -m venv .venv && pip install -r requirements.txt - •build:
python3 -m build(if applicable) - •run:
python3 -m uvicorn main:app(orpython3 main.py) - •test:
python3 -m pytest - •lint:
ruff check .orpython3 -m flake8 . - •Dockerfile: Multi-stage with
python:3.12-slim, venv copied to runtime
Rust
- •init:
cargo fetch - •build:
cargo build --release - •run:
cargo run - •test:
cargo test - •lint:
cargo clippy -- -D warnings - •Dockerfile: Multi-stage with
rust:1.77-alpine,distrolessruntime
Java
- •init:
mvn dependency:resolveorgradle dependencies - •build:
mvn package -DskipTestsorgradle build -x test - •run:
mvn exec:javaorgradle run - •test:
mvn testorgradle test - •lint:
mvn checkstyle:checkorgradle check - •Dockerfile: Multi-stage with Maven/Gradle builder, JRE runtime
Anti-patterns
- •Complex logic in Makefile -- Use scripts instead. Makefiles are hard to debug and have surprising whitespace rules.
- •Platform-specific commands without guards -- Use
require_cmdto check for required tools. - •Hardcoded paths -- Use variables derived from
PROJECT_ROOT. - •Secrets in scripts -- Use
.envfiles (git-ignored) or environment variables. Never hardcode credentials. - •Skipping
set -euo pipefail-- Always fail fast. Silent failures cause cascading problems.
Agent Integration
- •All operational tasks go through
maketargets. If a project has no Makefile, offer to scaffold one first using thescaffoldtool. - •Detect the project type automatically and tailor all generated files.
- •Scaffolding generates Makefile, modular scripts in
scripts/, container files, Cloud Build configs, and Terraform modules -- all incicd/. - •Use the
scaffoldtool directly. Do not delegate scaffolding to other agents.