CARGO := cargo
DOCKER := docker
DOCKER_COMPOSE := docker-compose
PROJECT_NAME := paladin
COMPOSE_FILE := docker/docker-compose.yml
COMPOSE_DEV_FILE := docker/docker-compose.dev.yml
COMPOSE_TEST_FILE := docker/docker-compose.test.yml
.DEFAULT_GOAL := help
CYAN := \033[0;36m
GREEN := \033[0;32m
YELLOW := \033[1;33m
RED := \033[0;31m
NC := \033[0m
.PHONY: help
help:
@awk 'BEGIN {FS = ":.*##"; printf "\n$(CYAN)Usage:$(NC)\n make $(YELLOW)<target>$(NC)\n"} /^[a-zA-Z_0-9-]+:[^#]*##/ { printf " $(GREEN)%-20s$(NC) %s\n", $$1, $$2 } /^##@/ { printf "\n$(CYAN)%s$(NC)\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
.PHONY: examples
examples:
@echo "$(CYAN)Common Usage Examples:$(NC)"
@echo ""
@echo "$(YELLOW)Development Workflow:$(NC)"
@echo " make setup @echo " make dev @echo " make watch @echo " make test-integration-minio @echo ""
@echo "$(YELLOW)Testing:$(NC)"
@echo " make test-all @echo " make test-integration-docker @echo " make ci-test @echo ""
@echo "$(YELLOW)Code Quality:$(NC)"
@echo " make clean-code @echo " make release VERSION=0.4.0 @echo " make publish-dry-run @echo " make audit @echo " make doc @echo ""
@echo "$(YELLOW)Services Management:$(NC)"
@echo " make services-up @echo " make redis-cli @echo " make minio-console @echo " make health
.PHONY: status
status:
@echo "$(CYAN)Project Status:$(NC)"
@echo ""
@echo "$(YELLOW)Build Status:$(NC)"
@$(CARGO) --version || echo "❌ Cargo not found"
@rustc --version || echo "❌ Rust not found"
@echo ""
@echo "$(YELLOW)Docker Status:$(NC)"
@$(DOCKER) --version || echo "❌ Docker not found"
@$(DOCKER_COMPOSE) --version || echo "❌ Docker Compose not found"
@echo ""
@echo "$(YELLOW)Services Status:$(NC)"
@$(MAKE) health
@echo ""
@echo "$(YELLOW)Git Status:$(NC)"
@git status --porcelain || echo "Not a git repository"
-include Makefile.local setup
setup:
@echo "$(CYAN)Setting up development environment...$(NC)"
@rustup update stable
@rustup component add rustfmt clippy
@$(CARGO) install cargo-audit cargo-watch cargo-expand
@$(CARGO) install --locked cargo-release
@$(CARGO) install --locked cargo-deny cargo-cyclonedx
@cp .env.example .env 2>/dev/null || echo ".env already exists"
@echo "$(GREEN)✅ Setup complete!$(NC)"
.PHONY: dev
dev:
@echo "$(CYAN)Starting development environment...$(NC)"
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) -f $(COMPOSE_DEV_FILE) up -d
@echo "$(GREEN)✅ Development environment started$(NC)"
@echo "Services available at:"
@echo " - Application: http://localhost:8080"
@echo " - MinIO Console: http://localhost:9001"
@echo " - Redis Commander: http://localhost:8081"
.PHONY: dev-logs
dev-logs:
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) -f $(COMPOSE_DEV_FILE) logs -f
.PHONY: watch
watch:
@echo "$(CYAN)Starting file watcher...$(NC)"
@$(CARGO) watch -x check -x test -x 'clippy --all-targets'
.PHONY: test
test:
@echo "$(CYAN)Running unit tests...$(NC)"
@$(CARGO) test --workspace --lib --bins
.PHONY: test-doc
test-doc:
@echo "$(CYAN)Running documentation tests...$(NC)"
@$(CARGO) test --workspace --doc
.PHONY: test-integration
test-integration:
@echo "$(CYAN)Running integration tests in local mode...$(NC)"
@./scripts/run_integration_tests.sh -m local
.PHONY: test-integration-docker
test-integration-docker:
@echo "$(CYAN)Running integration tests with docker-compose...$(NC)"
@./scripts/run_integration_tests.sh -m docker -v
.PHONY: test-integration-redis
test-integration-redis:
@echo "$(CYAN)Running Redis integration tests...$(NC)"
@./scripts/run_integration_tests.sh -t "redis" -m local
.PHONY: test-integration-minio
test-integration-minio:
@echo "$(CYAN)Running MinIO integration tests...$(NC)"
@./scripts/run_integration_tests.sh -t "file_storage" -m local
.PHONY: test-all
test-all: test test-doc test-integration
@echo "$(GREEN)✅ All tests completed!$(NC)"
.PHONY: test-ci
test-ci:
@echo "$(CYAN)Running tests in CI mode...$(NC)"
@./scripts/run_integration_tests.sh -m ci
.PHONY: test-core
test-core:
@echo "$(CYAN)Running tests for paladin-core...$(NC)"
@$(CARGO) test -p paladin-core
.PHONY: test-ports
test-ports:
@echo "$(CYAN)Running tests for paladin-ports...$(NC)"
@$(CARGO) test -p paladin-ports
.PHONY: test-battalion
test-battalion:
@echo "$(CYAN)Running tests for paladin-battalion...$(NC)"
@$(CARGO) test -p paladin-battalion
.PHONY: test-llm
test-llm:
@echo "$(CYAN)Running tests for paladin-llm...$(NC)"
@$(CARGO) test -p paladin-llm
.PHONY: test-memory
test-memory:
@echo "$(CYAN)Running tests for paladin-memory...$(NC)"
@$(CARGO) test -p paladin-memory
.PHONY: test-storage
test-storage:
@echo "$(CYAN)Running tests for paladin-storage...$(NC)"
@$(CARGO) test -p paladin-storage
.PHONY: test-notifications
test-notifications:
@echo "$(CYAN)Running tests for paladin-notifications...$(NC)"
@$(CARGO) test -p paladin-notifications
.PHONY: test-content
test-content:
@echo "$(CYAN)Running tests for paladin-content...$(NC)"
@$(CARGO) test -p paladin-content
.PHONY: test-web
test-web:
@echo "$(CYAN)Running tests for paladin-web...$(NC)"
@$(CARGO) test -p paladin-web
.PHONY: test-facade
test-facade:
@echo "$(CYAN)Running tests for paladin (facade)...$(NC)"
@$(CARGO) test -p paladin
.PHONY: fmt
fmt:
@echo "$(CYAN)Formatting code...$(NC)"
@$(CARGO) fmt --all
.PHONY: lint
lint:
@echo "$(CYAN)Running linter...$(NC)"
@$(CARGO) clippy --workspace --all-targets --all-features -- -D warnings
.PHONY: check
check:
@echo "$(CYAN)Checking code...$(NC)"
@$(CARGO) check --workspace --all-targets
.PHONY: audit
audit:
@echo "$(CYAN)Running security audit...$(NC)"
@ @$(CARGO) audit
.PHONY: deny
deny:
@echo "$(CYAN)Running cargo-deny check...$(NC)"
@$(CARGO) deny check
.PHONY: security
security: audit deny
.PHONY: sbom
sbom:
@echo "$(CYAN)Generating CycloneDX SBOM...$(NC)"
@command -v cargo-cyclonedx >/dev/null 2>&1 || { \
echo "$(RED)cargo-cyclonedx not found. Install with 'cargo install --locked cargo-cyclonedx'.$(NC)"; \
exit 1; \
}
@$(CARGO) cyclonedx --all --format json
@echo "$(GREEN)✅ SBOM generated (see bom.json / <crate>.cdx.json)$(NC)"
.PHONY: doc
doc:
@echo "$(CYAN)Generating documentation...$(NC)"
@$(CARGO) doc --workspace --no-deps --open
.PHONY: clean-code
clean-code: fmt lint check
.PHONY: hooks
hooks:
@echo "$(CYAN)Installing pre-commit hooks...$(NC)"
@command -v pre-commit >/dev/null 2>&1 || { \
echo "$(RED)pre-commit not found. Install it with 'pipx install pre-commit' (see CONTRIBUTING.md).$(NC)"; \
exit 1; \
}
@pre-commit install
@pre-commit install --hook-type pre-push
@echo "$(GREEN)✅ Git hooks installed (pre-commit + pre-push)$(NC)"
.PHONY: build
build:
@echo "$(CYAN)Building project...$(NC)"
@$(CARGO) build --workspace
.PHONY: build-release
build-release:
@echo "$(CYAN)Building release version...$(NC)"
@$(CARGO) build --release --workspace
.PHONY: build-docker
build-docker:
@echo "$(CYAN)Building Docker image...$(NC)"
@$(DOCKER) build -f docker/Dockerfile -t $(PROJECT_NAME):latest .
.PHONY: services-up
services-up:
@echo "$(CYAN)Starting all services...$(NC)"
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) up -d
@echo "$(GREEN)✅ Services started$(NC)"
.PHONY: services-down
services-down:
@echo "$(CYAN)Stopping all services...$(NC)"
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) down
@echo "$(GREEN)✅ Services stopped$(NC)"
.PHONY: services-restart
services-restart: services-down services-up
.PHONY: services-logs
services-logs:
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) logs -f
.PHONY: services-ps
services-ps:
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) ps
.PHONY: redis-cli
redis-cli:
@echo "$(CYAN)Connecting to Redis...$(NC)"
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) exec redis redis-cli
.PHONY: minio-console
minio-console:
@echo "$(CYAN)Opening MinIO console...$(NC)"
@echo "MinIO Console: http://localhost:9001"
@echo "Credentials: minioadmin/minioadmin"
.PHONY: db-reset
db-reset:
@echo "$(CYAN)Resetting database...$(NC)"
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) exec paladin-app rm -f database.db || true
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) restart paladin-app
.PHONY: storage-reset
storage-reset:
@echo "$(CYAN)Resetting MinIO storage...$(NC)"
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) exec minio rm -rf /data/* || true
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) restart minio minio-init
.PHONY: data-reset
data-reset: db-reset storage-reset
.PHONY: clean
clean:
@echo "$(CYAN)Cleaning build artifacts...$(NC)"
@$(CARGO) clean
@$(DOCKER) system prune -f
.PHONY: clean-docker
clean-docker:
@echo "$(CYAN)Cleaning Docker resources...$(NC)"
@$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) down -v --remove-orphans
@$(DOCKER_COMPOSE) -f $(COMPOSE_TEST_FILE) down -v --remove-orphans || true
@$(DOCKER) system prune -f
.PHONY: deps-update
deps-update:
@echo "$(CYAN)Updating dependencies...$(NC)"
@$(CARGO) update
.PHONY: deps-tree
deps-tree:
@$(CARGO) tree
.PHONY: size
size:
@echo "$(CYAN)Binary sizes:$(NC)"
@ls -lh target/release/$(PROJECT_NAME) 2>/dev/null || echo "No release binary found. Run 'make build-release' first."
.PHONY: ci-setup
ci-setup:
@echo "$(CYAN)Setting up CI environment...$(NC)"
@rustup component add rustfmt clippy
.PHONY: ci-test
ci-test:
@echo "$(CYAN)Running CI test suite...$(NC)"
@$(MAKE) clean-code
@$(MAKE) test
@$(MAKE) test-doc
@$(MAKE) audit
@$(MAKE) test-ci
.PHONY: release-check
release-check:
@echo "$(CYAN)Checking release readiness...$(NC)"
@$(MAKE) clean-code
@$(MAKE) test
@echo "$(CYAN)Running doc tests (excluding paladin-ports: doctests reference root crate not yet published)...$(NC)"
@$(CARGO) test --workspace --doc --exclude paladin-ports
@$(MAKE) audit
@$(MAKE) build-release
@echo "$(GREEN)✅ Release check passed!$(NC)"
.PHONY: publish-dry-run
publish-dry-run: release-check
@echo "$(CYAN)Running dependency-first publish dry-runs...$(NC)"
@$(CARGO) publish --dry-run -p paladin-core || true
@$(CARGO) publish --dry-run -p paladin-ports || true
@$(CARGO) publish --dry-run -p paladin-battalion || true
@$(CARGO) publish --dry-run -p paladin-llm || true
@$(CARGO) publish --dry-run -p paladin-memory || true
@$(CARGO) publish --dry-run -p paladin-web || true
@$(CARGO) publish --dry-run -p paladin-notifications || true
@$(CARGO) publish --dry-run -p paladin-content || true
@$(CARGO) publish --dry-run -p paladin-storage || true
@$(CARGO) publish --dry-run -p paladin || true
@echo "$(YELLOW)Dry-run publish command sequence completed. See docs/RELEASE_CHECKLIST.md for interpretation and publish-order gating.$(NC)"
.PHONY: release
release: ## Cut a release: bump version (lockstep), finalize changelog, commit, tag, push. Usage: make release VERSION=0.4.0
@if [ -z "$(VERSION)" ]; then \
echo "$(RED)❌ VERSION is required. Usage: make release VERSION=0.4.0$(NC)"; \
exit 1; \
fi
@echo "$(VERSION)" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$$' || { \
echo "$(RED)❌ VERSION '$(VERSION)' is not a valid semver string (e.g. 0.4.0 or 0.4.0-rc.1).$(NC)"; \
exit 1; \
}
@command -v cargo-release >/dev/null 2>&1 || { \
echo "$(RED)❌ cargo-release not found. Install with 'cargo install --locked cargo-release'.$(NC)"; \
exit 1; \
}
@# Release-branch protection (Milestone 10 Epic 5): tags may only be cut from
@ @ @ @if [ "$(RELEASE_ALLOW_ANY_BRANCH)" = "1" ]; then \
echo "$(YELLOW)⚠ RELEASE_ALLOW_ANY_BRANCH=1 — skipping main-branch check (CI still enforces main-only).$(NC)"; \
else \
CURRENT_BRANCH=$$(git rev-parse --abbrev-ref HEAD); \
if [ "$$CURRENT_BRANCH" != "main" ]; then \
echo "$(RED)❌ Releases must be cut from 'main' (current branch: $$CURRENT_BRANCH).$(NC)"; \
echo "$(RED) Merge your changes via PR, then 'git checkout main && git pull --ff-only'.$(NC)"; \
echo "$(RED) Override (hotfix only): RELEASE_ALLOW_ANY_BRANCH=1. See docs/BRANCH_PROTECTION.md.$(NC)"; \
exit 1; \
fi; \
fi
@echo "$(CYAN)Verifying local branch is up to date with origin/main...$(NC)"
@git fetch --quiet origin main || { echo "$(RED)❌ Failed to fetch origin/main.$(NC)"; exit 1; }
@if [ -n "$$(git rev-list HEAD..origin/main)" ]; then \
echo "$(RED)❌ HEAD is behind origin/main. Run 'git pull --ff-only origin main' before releasing.$(NC)"; \
exit 1; \
fi
@echo "$(CYAN)Cutting release v$(VERSION)...$(NC)"
@$(MAKE) release-check
@echo "$(CYAN)Bumping all crates to $(VERSION) (lockstep)...$(NC)"
@$(CARGO) release version "$(VERSION)" --execute --no-confirm --workspace
@echo "$(CYAN)Finalizing CHANGELOG.md...$(NC)"
@DATE=$$(date +%Y-%m-%d); \
perl -0pi -e "s/## \\[Unreleased\\]/## [Unreleased]\n\n## [$(VERSION)] - $$DATE/" CHANGELOG.md
@echo "$(CYAN)Committing, tagging, and pushing...$(NC)"
@git add -u
@git commit -m "chore(release): version $(VERSION)"
@git tag -a "v$(VERSION)" -m "Release $(VERSION)"
@git push origin HEAD
@git push origin "v$(VERSION)"
@echo "$(GREEN)✅ Release v$(VERSION) tagged and pushed. CI (release.yml) will publish to crates.io.$(NC)"
.PHONY: health
health:
@echo "$(CYAN)Checking service health...$(NC)"
@echo "Redis:"
@curl -f http://localhost:6379 2>/dev/null && echo "✅ Redis OK" || echo "❌ Redis DOWN"
@echo "MinIO:"
@curl -f http://localhost:9000/minio/health/live 2>/dev/null && echo "✅ MinIO OK" || echo "❌ MinIO DOWN"
@echo "Application:"
@curl -f http://localhost:8080/health 2>/dev/null && echo "✅ App OK" || echo "❌ App DOWN or no health endpoint"
.PHONY: bench
bench:
@echo "$(CYAN)Running benchmarks...$(NC)"
@$(CARGO) bench --workspace
.PHONY: profile
profile:
@echo "$(CYAN)Profiling application...$(NC)"
@$(CARGO) build --release
@echo "Run: perf record target/release/$(PROJECT_NAME)"
@echo "Then: perf report"
.PHONY: devcontainer-build
devcontainer-build:
@echo "$(CYAN)Building DevContainer image...$(NC)"
@$(DOCKER) build -f .devcontainer/Dockerfile.dev -t $(PROJECT_NAME)-devcontainer:latest .
@echo "$(GREEN)✅ DevContainer image built$(NC)"
.PHONY: devcontainer-validate
devcontainer-validate:
@echo "$(CYAN)Validating DevContainer...$(NC)"
@.devcontainer/validate.sh
.PHONY: devcontainer-network
devcontainer-network:
@.devcontainer/setup-network.sh
.PHONY: devcontainer-services
devcontainer-services:
@echo "$(CYAN)Starting DevContainer services...$(NC)"
@$(DOCKER_COMPOSE) -f .devcontainer/docker-compose.yml up -d redis minio mysql
@echo "$(GREEN)✅ Services started$(NC)"
.PHONY: devcontainer-services-down
devcontainer-services-down:
@echo "$(CYAN)Stopping DevContainer services...$(NC)"
@$(DOCKER_COMPOSE) -f .devcontainer/docker-compose.yml down
@echo "$(GREEN)✅ Services stopped$(NC)"
.PHONY: devcontainer-push
devcontainer-push:
@echo "$(CYAN)Pushing DevContainer image...$(NC)"
@$(DOCKER) tag $(PROJECT_NAME)-devcontainer:latest ghcr.io/df3ndr/$(PROJECT_NAME)-devcontainer:latest
@$(DOCKER) push ghcr.io/df3ndr/$(PROJECT_NAME)-devcontainer:latest
@echo "$(GREEN)✅ Image pushed$(NC)"
.PHONY: