Files
vociferate/.github/copilot-instructions.md

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.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:

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:

// 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:

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:

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:

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:

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.