This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a Go-based observability analysis tools repository containing six CLI tools for PromQL-compatible monitoring systems:
- promql-fmt: PromQL expression formatter and validator
- label-check: Label standards enforcement tool
- alert-hysteresis: Alert firing pattern analyzer
- autogen-promql-tests: PromQL test case generator
- e2e-alertmanager-test: End-to-end Alertmanager testing tool
- stale-alerts-analyzer: Alert staleness analyzer
The repository uses standard Go project structure, GitHub Actions for CI/CD, and GoReleaser for multi-platform releases.
Default workflow: Create feature branches off main.
- Use descriptive branch names:
feat/description,fix/description,chore/description - GitHub Actions CI runs on all branches matching
claude/**and onmain - Pull requests should target
mainbranch - Releases are automated via git tags matching
v*pattern
- When creating Pull Requests, include the primary prompts as multiline markdown code blocks in the PR description
- This provides context for reviewers about the original intent and scope
- Format prompts using markdown code fences (triple backticks)
- Example format in PR description:
Create a new branch to update @dot_claude/CLAUDE.md to use subagents...
## Original Prompt
cmd/ # CLI entry points (main packages)
promql-fmt/ # PromQL formatter tool
label-check/ # Label validation tool
alert-hysteresis/ # Alert analysis tool
autogen-promql-tests/ # PromQL test case generator
e2e-alertmanager-test/ # End-to-end Alertmanager testing tool
stale-alerts-analyzer/ # Alert staleness analyzer
pkg/ # Public libraries (importable by external projects)
formatting/ # PromQL formatting logic
internal/ # Private libraries (not importable externally)
promql/ # PromQL parsing utilities
alertmanager/ # Prometheus/Alertmanager integration
- Unit tests co-located with source:
*_test.gofiles - Test packages mirror source structure
- Use table-driven tests for multiple test cases
- Coverage tracked via Codecov on ubuntu-latest + Go 1.21
To minimize Claude token usage and enable rapid iteration, always detect and run CI checks locally before committing.
Read .github/workflows/test.yml to identify:
- Test commands (Go version matrix, test flags, build commands)
- Lint commands (golangci-lint version and configuration)
- Other checks (GoReleaser config validation)
Run these commands in the exact same way as CI:
# 1. Download dependencies (as CI does)
go mod download
# 2. Run tests with race detection and coverage (matches CI exactly)
go test -v -race -coverprofile=./coverage.txt ./...
# 3. Build all binaries (matches CI build step)
go build -o bin/ ./cmd/promql-fmt
go build -o bin/ ./cmd/label-check
go build -o bin/ ./cmd/alert-hysteresis
go build -o bin/ ./cmd/autogen-promql-tests
go build -o bin/ ./cmd/e2e-alertmanager-test
go build -o bin/ ./cmd/stale-alerts-analyzer
# 4. Run golangci-lint (matches CI lint job)
golangci-lint run
# 5. Validate GoReleaser config (matches CI goreleaser-check job)
# Note: Only if goreleaser is installed locally
goreleaser check 2>/dev/null || echo "goreleaser not installed, will validate in CI"For convenience, you can create and use this script to run all CI checks:
#!/bin/bash
# scripts/ci-check.sh - Run all CI checks locally
set -e
echo "==> Running CI checks locally..."
echo "==> 1. Downloading dependencies..."
go mod download
echo "==> 2. Running tests with race detection..."
go test -v -race -coverprofile=./coverage.txt ./...
echo "==> 3. Building binaries..."
mkdir -p bin
go build -o bin/ ./cmd/promql-fmt
go build -o bin/ ./cmd/label-check
go build -o bin/ ./cmd/alert-hysteresis
go build -o bin/ ./cmd/autogen-promql-tests
go build -o bin/ ./cmd/e2e-alertmanager-test
go build -o bin/ ./cmd/stale-alerts-analyzer
echo "==> 4. Running golangci-lint..."
if command -v golangci-lint > /dev/null; then
golangci-lint run
else
echo "WARNING: golangci-lint not installed, skipping lint check"
echo "Install from: https://golangci-lint.run/usage/install/"
fi
echo "==> 5. Validating GoReleaser config..."
if command -v goreleaser > /dev/null; then
goreleaser check
else
echo "WARNING: goreleaser not installed, skipping config validation"
fi
echo "==> All CI checks passed! ✓"ALWAYS run these checks before creating a commit:
# Quick check (tests + lint)
go test ./... && golangci-lint run
# Full check (matches CI exactly)
./scripts/ci-check.sh
# Or use Makefile targets
make test && make lint- Token Efficiency: Catching errors locally avoids expensive back-and-forth with CI failures
- Faster Iteration: Local checks run in seconds vs. minutes for CI
- Context Preservation: Fixing issues immediately while context is fresh
- CI Confidence: If local checks pass, CI will almost certainly pass
-
Before making changes:
# Ensure dependencies are current go mod download go mod tidy # Run existing tests to ensure baseline go test ./...
-
While implementing features:
# Run tests for specific package go test ./pkg/formatting -v go test ./internal/promql -v # Quick format check go fmt ./...
-
Before committing:
# Run full CI check suite go mod download go test -v -race -coverprofile=./coverage.txt ./... go build -o bin/ ./cmd/promql-fmt go build -o bin/ ./cmd/label-check go build -o bin/ ./cmd/alert-hysteresis go build -o bin/ ./cmd/autogen-promql-tests go build -o bin/ ./cmd/e2e-alertmanager-test go build -o bin/ ./cmd/stale-alerts-analyzer golangci-lint run # Or use Makefile make test && make lint && make build
-
Creating commits:
git add . git commit -m "feat: description Detailed explanation of changes. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
Always write tests when:
- Adding new functions or methods
- Modifying existing logic
- Fixing bugs (add regression test)
Test file naming:
- Unit tests:
filename_test.goin same package - Test package name: same as source package or
package_testfor black-box tests
Running tests:
# All tests
go test ./...
# With verbose output
go test -v ./...
# Specific package
go test ./pkg/formatting
# With coverage
go test -coverprofile=coverage.txt ./...
go tool cover -html=coverage.txt
# With race detection (matches CI)
go test -race ./...The project uses golangci-lint v2.8 with custom configuration in .golangci.yml.
Always run linter before committing:
# Run with project config
golangci-lint run
# Auto-fix issues where possible
golangci-lint run --fix
# Check specific directory
golangci-lint run ./cmd/...Configuration notes:
- Version must be v2.8+ (specified in
.golangci.ymlversion field) - Config uses v2 format (not compatible with golangci-lint < v2.8)
- Formatters (gofmt, goimports) are separate from linters in v2
# Build all tools to bin/ directory
make build
# Build specific tool
go build -o bin/promql-fmt ./cmd/promql-fmt
# Install to $GOPATH/bin
make install
# Clean build artifacts
make cleanThe promql-fmt tool validates against official Prometheus best practices from:
- https://prometheus.io/docs/practices/naming/
- https://prometheus.io/docs/practices/instrumentation/
- https://prometheus.io/docs/practices/alerting/
Key validations implemented:
-
Naming conventions:
- snake_case for metric names (not camelCase)
- Application prefix required (e.g.,
app_metric_name) - Base units (use
_seconds, not_milliseconds) - Counter suffix
_totalfor counters
-
Instrumentation:
rate()required on counters- Division protection (check denominator != 0)
-
Aggregation consistency:
- All
by/withoutclauses in same position (prefix or postfix) - Example:
sum(metric) by (label)vssum by (label) (metric)
- All
-
Line length:
- Configurable via
--disable-line-lengthflag - Useful for long metric names in recording rules
- Configurable via
The project uses GoReleaser v2 for releases:
Key configuration points:
- Config file:
.goreleaser.yml(version: 2 format) - Uses Docker buildx with deprecated
dockers/docker_manifests(notdockers_v2) - Reason: Podman support requires GoReleaser Pro; using buildx with Podman backend instead
- Homebrew formula published to
conallob/homebrew-tap - Container images pushed to
ghcr.io/conallob/*
Deprecation warnings (expected):
dockersanddocker_manifestsdeprecated in favor ofdockers_v2- Keeping deprecated version because it supports
use: buildxin free version - Will migrate to
dockers_v2when Podman support added (currently Pro-only)
Testing releases locally:
# Validate config
goreleaser check
# Build snapshot (no publish)
goreleaser release --snapshot --clean
# Test specific builds
goreleaser build --single-target --snapshot- go.mod/go.sum: Go module dependencies
- .golangci.yml: golangci-lint v2 configuration
- .goreleaser.yml: Release automation config (GoReleaser v2)
- Makefile: Build automation targets
- .github/workflows/test.yml: Test/lint/build workflow (runs on push/PR)
- .github/workflows/release.yml: Release workflow (runs on version tags)
- cmd/*/main.go: CLI entry points for each tool
- pkg/formatting/promql.go: Core PromQL formatting logic
- internal/: Private implementation packages
The tools build for:
- Linux: amd64, arm64
- macOS (Darwin): amd64, arm64
- Windows: amd64, arm64
GitHub Actions tests on:
- OS: ubuntu-latest, macos-latest, windows-latest
- Go versions: 1.21, 1.22
Windows-specific considerations:
- Coverage file path must use
./prefix:./coverage.txt - Without prefix, Windows interprets
.txtas package name
Built with Docker buildx using Podman backend:
- Multi-arch support (linux/amd64, linux/arm64)
- Separate images per tool
- Multi-arch manifests for version and latest tags
-
Read existing tests to understand patterns:
# Example: adding validation to promql-fmt Read pkg/formatting/promql_test.go -
Implement feature with tests:
// Add implementation in pkg/formatting/promql.go // Add tests in pkg/formatting/promql_test.go
-
Run local CI checks:
go test -v -race ./pkg/formatting golangci-lint run ./pkg/formatting go test -v -race ./... # Full suite
-
Commit when all checks pass:
git add . git commit -m "feat(promql-fmt): add new validation"
-
Write failing test that reproduces bug:
// Add regression test in appropriate *_test.go func TestBugFix(t *testing.T) { // Test case that currently fails }
-
Fix the bug:
// Implement fix in source file -
Verify fix:
go test -v ./pkg/formatting # Should now pass go test -race ./... # Full test suite golangci-lint run # No new issues
-
Commit with reference to issue:
git commit -m "fix: resolve issue with metric name validation Fixes incorrect handling of metric names with underscores. Added regression test. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
# Update all dependencies to latest compatible versions
go get -u ./...
go mod tidy
# Update specific dependency
go get github.com/example/pkg@latest
go mod tidy
# Verify everything still works
go test ./...
go build ./cmd/...-
Ensure all tests pass:
go test -v -race ./... golangci-lint run goreleaser check -
Create and push tag:
git tag -a v1.2.3 -m "Release v1.2.3: description" git push origin v1.2.3 -
GitHub Actions will automatically:
- Build binaries for all platforms
- Create deb/rpm packages
- Build and push container images
- Update Homebrew formula
- Create GitHub release
go mod tidy
go mod download- Check
.golangci.ymlversion field is"2"(string, not number) - Ensure golangci-lint version is v2.8+
- Separate linters and formatters in v2 config
- Validate config:
goreleaser check - Check deprecation warnings (some are expected)
- Ensure
version: 2in.goreleaser.yml
- Indicates potential data race
- Fix by adding proper synchronization (mutexes, channels)
- Never disable race detector to hide issues
- Use
./coverage.txtnotcoverage.txt - Windows needs explicit relative path prefix
- Formatting: Use
go fmt(enforced by CI) - Comments:
- Package comments required (enforced by revive linter)
- Public functions should have doc comments
- Use
//for single-line,/* */for multi-line
- Naming:
- Use camelCase for variables (not snake_case)
- Use PascalCase for exported identifiers
- Avoid single-letter variables except in loops
- Error handling:
- Always check errors
- Don't capitalize error strings
- Use
fmt.Errorfwith%wfor wrapping
<type>: <short summary>
<detailed description>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Types: feat, fix, docs, test, refactor, chore, perf
Standard library focused - minimize external dependencies:
- No web frameworks required
- YAML parsing: use standard library or minimal deps
- HTTP clients: use
net/httpfrom stdlib
Current key dependencies (check go.mod for versions):
- Prometheus client/API libraries (for alert-hysteresis)
- YAML parsing (for reading Prometheus rules)
-
Check Go version mismatch:
go version # Should match CI matrix (1.21 or 1.22) -
Check OS-specific issues:
- Test on all platforms if possible
- Watch for path separator differences (/ vs )
- Windows line endings (CRLF vs LF)
-
Check race detector:
go test -race ./... # CI always uses -race
CI uses v2.8. Install locally:
# macOS
brew install golangci-lint
# Linux/WSL
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.8.0
# Verify version
golangci-lint --version # Should be v2.8.x- Prometheus Best Practices: https://prometheus.io/docs/practices/
- Go Testing: https://go.dev/doc/tutorial/add-a-test
- golangci-lint: https://golangci-lint.run/
- GoReleaser: https://goreleaser.com/
- GitHub Actions: https://docs.github.com/en/actions
- ✅ ALWAYS run local CI checks before committing (saves tokens!)
- ✅ ALWAYS write tests for new code (required by CI)
- ✅ ALWAYS run golangci-lint (CI will fail otherwise)
- ✅ ALWAYS use go fmt (enforced automatically)
- ✅ ALWAYS check coverage (aim for > 80% on new code)
⚠️ NEVER skip race detector (-raceflag)⚠️ NEVER commit without testing (even for "simple" changes)⚠️ NEVER ignore linter warnings (fix or explicitly disable with comment)