17 KiB
Æ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.gofiles (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 <TestName> ./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:
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:
- Update the changelog (typically
CHANGELOG.md). - Create a separate docs-only commit for changelog updates.
- 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): <summary> - Preferred format for documentation updates:
docs: <summary> - Keep summaries lowercase, imperative, and under 72 characters when possible.
- Use one commit per logical change.
Examples:
chore(go): update dependency injection guidancedocs: 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.gofiles 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<TypeName>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:
// 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,repositorytop-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()orfmt.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:
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
interfacespackages. - Consider a
cmd/directory for application entry points andinternal/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.Contextfor cancellation and timeouts. - Protect shared state with mutexes only when channels are not practical.
Example:
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, orcommonpackages; prefer domain-specific package names. - Keep files focused; one struct per file is reasonable if the struct is substantial.
- Use
context.Contextas 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@v5with pinned version). - Enforce Go dependency/build caching in every Go CI job to reduce repeated module and build downloads.
- Require
actions/setup-go@v5caching withcache: trueandcache-dependency-path: go.sum. - For workflows that split jobs across multiple Go-related steps (test/lint/security), ensure caches are restored in each job.
- Require
- Enforce formatting in local and CI workflows:
- Require
go fmt ./...before commit. - Require formatting validation in CI (for example
test -z "$(gofmt -l .)"), or use a standard formatter action that provides equivalent enforcement.
- Require
- Enforce module hygiene in local and CI workflows:
- Require
go mod tidyandgo mod verifyas part of validation. - CI may use standard actions/automation that perform equivalent module tidy and verification checks.
- Require
- Enforce changelog gate in PR validation workflows:
- Fail PR validation when no entry is added under
## [Unreleased]inCHANGELOG.mdfor code, behavior, security, workflow, or tooling changes. - Repository policy may allow explicit docs-only/metadata-only exceptions.
- Fail PR validation when no entry is added under
- Keep workflow summary output using the summary-file pattern:
- Define
SUMMARY_FILEenvironment variable per job. - Append markdown output from steps to the summary file.
- Print the summary in a final
Summarystep withif: ${{ always() }}condition.
- Define
- 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_PARAMSis optional; available params arebranch,event,stylefor badge URLs andbranch,eventfor badge-link targets. Preferbranchandeventwhen filtering run context; ifstyleis used, place it last.- Prefer latest-run pages for badge links for fast status triage.
Required Go Security Actions and Caching Pattern (GitHub Actions)
When using GitHub Actions for Go repositories, explicitly use these actions in CI:
securego/gosec@v2golang/govulncheck-action@v1
Minimum recommended pattern:
jobs:
security:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go with cache
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
cache-dependency-path: go.sum
- name: Validate formatting
run: |
set -euo pipefail
test -z "$(gofmt -l .)"
- name: Module hygiene
run: |
set -euo pipefail
go mod tidy
go mod verify
- name: Run gosec
uses: securego/gosec@v2
with:
args: ./...
- name: Run govulncheck
id: govulncheck
uses: golang/govulncheck-action@v1
with:
go-package: ./...
cache: true
cache-dependency-path: go.sum
Composite Actions and Release Orchestration
Use https://git.hrafn.xyz/aether/vociferate as the default release-management tool when integrating Æther composite actions:
- Always use full
https://URLs inuses:references for all vociferate actions (for exampleuses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.0.2). This ensures correct action resolution on both GitHub and self-hosted Gitea instances. Never use shorthand coordinates likeaether/vociferatewithout the full URL. - Pin all action references to released tags (for example
@v1.0.2). - Keep all vociferate references on the same tag within a workflow.
- Use
prepareaction to update changelog/version and create release tags. - Use
publishaction 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-badgeaction after tests producecoverage.outfor 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:
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
justfilefor task automation; mirror core CI operations locally. - Dependency management: Use
go.modandgo.sumfor version tracking. - Code formatting: Run
go fmt ./...before committing changes. - Module hygiene: Run
go mod tidyandgo mod verifyduring local validation. - 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
#noseccomments only with documented justification.
- Command:
-
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.
- Command:
-
GitHub Actions enforcement (for GitHub-hosted CI):
- Use
securego/gosec@v2in CI workflows. - Use
golang/govulncheck-action@v1in CI workflows. - Enable caching in these workflows (
actions/setup-go@v5withcache: trueandcache-dependency-path).
- Use
-
Dependency hygiene: Keep
go.modandgo.sumclean; rungo mod tidyandgo mod verifyregularly.
Integrate both tools into CI workflows; fail builds on high/critical findings.
Validation Sequence
Execute validation in this order (unless repository policy specifies otherwise):
- Run
go fmt ./...for code formatting. - Validate formatting (for example
test -z "$(gofmt -l .)") before or within CI. - Run
go mod tidyandgo mod verify(or equivalent standard automation). - Run focused package tests that directly cover the changed code.
- Run broader package or module test suites as needed.
- Run
gosec ./...for security analysis. - Run
govulncheck ./...for vulnerability scanning. - Run full project or behavior/integration suites when change scope or risk warrants it.
- 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.
- ✓ Code was formatted with
go fmt ./...and formatting validation passed. - ✓ Module hygiene checks passed (
go mod tidyandgo mod verify, or equivalent standard automation). - ✓ PR validation changelog gate passed (
CHANGELOG.mdhas required addition under## [Unreleased]when policy applies). - ✓ 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 ./...andgovulncheck ./...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.