441 lines
17 KiB
Markdown
441 lines
17 KiB
Markdown
# Æ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 <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:
|
|
|
|
```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): <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 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<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:
|
|
|
|
```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).
|
|
- Enforce Go dependency/build caching in every Go CI job to reduce repeated module and build downloads.
|
|
- Require `actions/setup-go@v5` caching with `cache: true` and `cache-dependency-path: go.sum`.
|
|
- For workflows that split jobs across multiple Go-related steps (test/lint/security), ensure caches are restored in each job.
|
|
- 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.
|
|
- Enforce module hygiene in local and CI workflows:
|
|
- Require `go mod tidy` and `go mod verify` as part of validation.
|
|
- CI may use standard actions/automation that perform equivalent module tidy and verification checks.
|
|
- Enforce changelog gate in PR validation workflows:
|
|
- Fail PR validation when no entry is added under `## [Unreleased]` in `CHANGELOG.md` for code, behavior, security, workflow, or tooling changes.
|
|
- Repository policy may allow explicit docs-only/metadata-only exceptions.
|
|
- 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.
|
|
|
|
### Required Go Security Actions and Caching Pattern (GitHub Actions)
|
|
|
|
When using GitHub Actions for Go repositories, explicitly use these actions in CI:
|
|
|
|
- `securego/gosec@v2`
|
|
- `golang/govulncheck-action@v1`
|
|
|
|
Minimum recommended pattern:
|
|
|
|
```yaml
|
|
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** in `uses:` references for all vociferate actions (for example `uses: 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 like `aether/vociferate` without 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 `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.
|
|
- **Code formatting**: Run `go fmt ./...` before committing changes.
|
|
- **Module hygiene**: Run `go mod tidy` and `go mod verify` during 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 `#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.
|
|
|
|
- **GitHub Actions enforcement** (for GitHub-hosted CI):
|
|
- Use `securego/gosec@v2` in CI workflows.
|
|
- Use `golang/govulncheck-action@v1` in CI workflows.
|
|
- Enable caching in these workflows (`actions/setup-go@v5` with `cache: true` and `cache-dependency-path`).
|
|
|
|
- **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 `go fmt ./...` for code formatting.
|
|
2. Validate formatting (for example `test -z "$(gofmt -l .)"`) before or within CI.
|
|
3. Run `go mod tidy` and `go mod verify` (or equivalent standard automation).
|
|
4. Run focused package tests that directly cover the changed code.
|
|
5. Run broader package or module test suites as needed.
|
|
6. Run `gosec ./...` for security analysis.
|
|
7. Run `govulncheck ./...` for vulnerability scanning.
|
|
8. Run full project or behavior/integration suites when change scope or risk warrants it.
|
|
9. 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 tidy` and `go mod verify`, or equivalent standard automation).
|
|
- ✓ PR validation changelog gate passed (`CHANGELOG.md` has 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 ./...` 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.
|