# Æther Go Project Workflow Instructions These instructions apply to all coding work in Æther Go repositories. This file is self-contained. Do not assume access to this Guides repository or any other Æther repository when following these instructions. ## Engineering Process: Strict TDD Follow TDD (Red, Green, Refactor) for all feature and bug-fix work: - **Red**: Write or update a test first. Confirm the test fails for the expected reason. - **Green**: Implement the minimum code needed to pass the test. - **Refactor**: Improve code only after tests are green. Do not implement behavior before a failing test exists, unless the user explicitly asks to skip TDD. ## Testing Requirements - Always create and update tests in `*_test.go` files (Go language standard). - Use the repository's established test framework (prefer testify suites for new coverage where appropriate). - Run focused tests for touched packages first using `go test -run ./path/to/package`. - Run broader package or module suites as needed. - Run full project validation when requested or when change risk warrants it. - Preserve behavior validated by the repository's behavior/integration suites unless a behavioral change is explicitly requested. ## Coverage Standards Apply coverage gates per package/module, not repository-wide aggregate: - **Target**: 80%+ coverage per module. - **Warning zone**: 65% to 79.99% (below target, improve during normal engineering work). - **High risk**: 50% to 64.99% (requires explicit justification and follow-up). - **Fail gate**: Below 50% (unacceptable). Exclude generated code from coverage calculations. Run coverage analysis for changed modules: ```bash go test -covermode=atomic -coverprofile=coverage.out ./... ``` ## Commit Checkpoints Create commits at these checkpoints unless the user explicitly asks not to commit: - After writing failing tests (red commit). - After implementation passes tests (green commit). - After refactoring (if substantial refactoring occurred). - After changelog updates (separate docs-only commit). For each functional change block, create at least one code commit before moving to unrelated work. Keep commits small and scoped to one change unit. Use non-interactive git commands. ### Changelog Commits After completing a change block: 1. Update the changelog (typically `CHANGELOG.md`). 2. Create a separate docs-only commit for changelog updates. 3. Keep changelog commits scoped to documentation changes only—do not mix code edits into that commit. ### Commit Message Guidance Prefer conventional commits with clear scopes and concise summaries. - Preferred format for Go maintenance and tooling changes: `chore(go): ` - Preferred format for documentation updates: `docs: ` - Keep summaries lowercase, imperative, and under 72 characters when possible. - Use one commit per logical change. Examples: - `chore(go): update dependency injection guidance` - `docs: clarify security scanning requirements` ## Go Conventions - **Go version**: Target the repository's standard Go toolchain (typically `1.26.1`) and maintain compatibility with declared repository settings. - **Test files**: Keep tests in `*_test.go` files in the same package as the code being tested. - **Test suites**: Prefer testify suites for new Go test coverage where appropriate. - **Focused testing**: Run package-specific tests first, then broader validation when requested. - **Behavior parity**: Use the repository behavior suite for behavior parity validation when relevant. ## Dependency Injection (DI) Pattern Dependency Injection is a required architectural pattern in Æther Go projects. Use the standards below directly: - **Interfaces as contracts**: Define interfaces to represent dependencies; place interfaces in the same package as consumers. - **Concrete structs**: Implement concrete types as structs that satisfy interfaces. - **Constructor functions**: Use `New` constructor functions to wire dependencies and validate inputs. - **No globals or singletons**: Accept dependencies as parameters; avoid hidden global state. - **Testing with DI**: Create concrete mock implementations of interfaces for testing; avoid reflection-based mocking. Example pattern: ```go // Interface defines a contract. type UserRepository interface { GetUser(ctx context.Context, id string) (*User, error) } // Concrete implementation satisfies the interface. type PostgresUserRepository struct { db *sql.DB } func (r *PostgresUserRepository) GetUser(ctx context.Context, id string) (*User, error) { // Implementation } // Constructor function injects dependencies. func NewPostgresUserRepository(db *sql.DB) (*PostgresUserRepository, error) { if db == nil { return nil, errors.New("database connection cannot be nil") } return &PostgresUserRepository{db: db}, nil } // Consumer type accepts interface dependency. type UserService struct { repo UserRepository } func NewUserService(repo UserRepository) (*UserService, error) { if repo == nil { return nil, errors.New("UserRepository cannot be nil") } return &UserService{repo: repo}, nil } ``` Key rules: - Validate injected dependencies in constructors; return error if validation fails. - Keep interfaces minimal and focused on the consumer's contract (Interface Segregation Principle). - Organize packages by domain, not by layer (avoid `service`, `handler`, `repository` top-level packages). - Break circular dependencies using interfaces; define the interface in the consuming package. ## Additional Go Conventions Beyond dependency injection, follow these Go-specific conventions: ### Error Handling - Return errors as the last return value. - Use `errors.New()` or `fmt.Errorf()` for simple errors; use custom error types for complex cases. - Wrap errors with context using `fmt.Errorf("%w", err)` in Go 1.13+. - Do not log and return errors; let the caller decide how to handle. Example: ```go func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) { if id == "" { return nil, fmt.Errorf("GetUser: invalid user id") } user, err := s.repo.GetUser(ctx, id) if err != nil { return nil, fmt.Errorf("GetUser: %w", err) } return user, nil } ``` ### Package Organization - Use domain-driven design principles; packages represent domain entities or use cases. - Keep package dependencies acyclic; use interfaces to break circular dependencies. - Place interfaces in consumer packages, not separate `interfaces` packages. - Consider a `cmd/` directory for application entry points and `internal/` for domain logic. Example structure: ``` myapp/ cmd/ myapp/ main.go # Dependency wiring internal/ user/ user.go # Domain model service.go # UserService and UserRepository interface repository.go # PostgresUserRepository auth/ auth.go # Domain model service.go # AuthService and AuthProvider interface ``` ### Concurrency Patterns - Use goroutines and channels for concurrent work. - Prefer message passing (channels) over shared memory. - Use `context.Context` for cancellation and timeouts. - Protect shared state with mutexes only when channels are not practical. Example: ```go func (s *UserService) ProcessUsers(ctx context.Context, ids []string) error { workers := 5 jobs := make(chan string, len(ids)) errors := make(chan error, len(ids)) for i := 0; i < workers; i++ { go func() { for id := range jobs { if err := s.processUser(ctx, id); err != nil { errors <- err } } }() } for _, id := range ids { jobs <- id } close(jobs) for i := 0; i < len(ids); i++ { select { case err := <-errors: if err != nil { return err } case <-ctx.Done(): return ctx.Err() } } return nil } ``` ### Code Organization and Naming - Use clear, descriptive names for types and functions. - Avoid `util`, `helper`, or `common` packages; prefer domain-specific package names. - Keep files focused; one struct per file is reasonable if the struct is substantial. - Use `context.Context` as the first parameter in functions that can block or make external calls. ## Workflow and Release Standards When updating CI workflows or release logic: - Use the repository's standard Go setup (typically `actions/setup-go@v5` with pinned version and go.sum caching). - Keep workflow summary output using the summary-file pattern: - Define `SUMMARY_FILE` environment variable per job. - Append markdown output from steps to the summary file. - Print the summary in a final `Summary` step with `if: ${{ always() }}` condition. - Badge URLs must use `https://{HOST}/{OWNER}/{REPO}/actions/workflows/{WORKFLOW_FILE}.yml/badge.svg{?CONTEXT_PARAMS}`. - Badge-link targets must use `https://{HOST}/{OWNER}/{REPO}/actions/runs/latest?workflow={WORKFLOW_FILE}.yml{&CONTEXT_PARAMS}`. - `CONTEXT_PARAMS` is optional; available params are `branch`, `event`, `style` for badge URLs and `branch`, `event` for badge-link targets. Prefer `branch` and `event` when filtering run context; if `style` is used, place it last. - Prefer latest-run pages for badge links for fast status triage. ### Composite Actions and Release Orchestration Use `https://git.hrafn.xyz/aether/vociferate` as the default release-management tool when integrating Æther composite actions: - Pin all action references to released tags (for example `@v1.0.0`). - Keep all vociferate references on the same tag within a workflow. - In self-hosted runner environments (git.hrafn.xyz), use explicit `https://` action paths in `uses:` references and avoid shorthand owner/repo coordinates. - Use `prepare` action to update changelog/version and create release tags. - Use `publish` action to create/update release notes and assets from existing tags. - Do not mix alternate release actions unless a repository-local policy explicitly documents an override. - Use `coverage-badge` action after tests produce `coverage.out` for coverage artifact uploads. - For pre-conditions: checkout with full history (`fetch-depth: 0`), valid credentials, and required bucket variables/secrets. Minimal standalone release workflow example: ```yaml name: release on: push: branches: [main] permissions: contents: write jobs: prepare: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Vociferate prepare uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.0.0 publish: needs: prepare runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Vociferate publish uses: https://git.hrafn.xyz/aether/vociferate/publish@v1.0.0 ``` ## Project Conventions - **Automation**: Prefer `justfile` for task automation; mirror core CI operations locally. - **Dependency management**: Use `go.mod` and `go.sum` for version tracking. - **Structure**: Keep code organized in logical packages; avoid deep nesting. ## Security Standards Security standards (self-contained): - **gosec**: Run static security analysis for Go code. - Command: `gosec ./...` - Purpose: Detect common security issues (hard-coded secrets, SQL injection, weak crypto, etc.) - Suppress: Use `#nosec` comments only with documented justification. - **govulncheck**: Check Go code and dependencies for known vulnerabilities. - Command: `govulncheck ./...` - Purpose: Detect vulnerabilities in direct and transitive dependencies. - Address: Update vulnerable dependencies to patched versions. - **Dependency hygiene**: Keep `go.mod` and `go.sum` clean; run `go mod tidy` and `go mod verify` regularly. Integrate both tools into CI workflows; fail builds on high/critical findings. ## Validation Sequence Execute validation in this order (unless repository policy specifies otherwise): 1. Run focused package tests that directly cover the changed code. 2. Run broader package or module test suites as needed. 3. Run `gosec ./...` for security analysis. 4. Run `govulncheck ./...` for vulnerability scanning. 5. Run full project or behavior/integration suites when change scope or risk warrants it. 6. Verify coverage gates per changed module/class (target 80%, low bound 65%, fail below 50%). ## Safety and Scope - Do not revert unrelated local changes. - Avoid broad refactors outside the requested scope. - Keep implementation minimal and aimed only at passing the failing test. - Do not add code that the test does not exercise. ## Disambiguation and Decision-Making If blocked by ambiguity: - Ask one concise clarifying question rather than guessing. - Proceed with the most reasonable interpretation based on context. - Document any assumption in a commit message or code comment if relevant. ## Checklist: Feature or Bug-Fix Completion Before considering a task done: - ✓ A failing test existed before implementation (or skip-TDD was explicitly requested). - ✓ Implementation was minimal and aimed at passing the test only. - ✓ Refactoring happened only after tests were green. - ✓ Focused tests passed for all changed packages. - ✓ Broader validation was run when risk or scope justified it. - ✓ Coverage gates were evaluated per changed module/class (target 80%, low bound 65%, fail below 50%). - ✓ Behavioral parity expectations were preserved unless change was explicitly requested. - ✓ Security scanning passed: `gosec ./...` and `govulncheck ./...` without unacknowledged findings. - ✓ Dependency injection properly applied: dependencies injected via constructors, not globals; interfaces define contracts; concrete implementations satisfy interfaces. - ✓ Commits were created at checkpoints (red test, green implementation, changelog). - ✓ Changelog was updated with a separate docs-only commit.