This document outlines the coding standards and best practices for AI agents and developers working on the basic-setup project.
- Skills
- Work Snapshot Usage
- Scope Control
- Go Standards
- Bash Standards
- Documentation Standards
- Testing Standards
- Version Bumping and CHANGELOG
- Linting and Code Quality
Skills follow the Opencode/Project Agent skill format and live under .agents/skills/<name>/SKILL.md. See .agents/README.md for the instructions index.
Use .agents/work-snapshot.local.md as a local handoff aid, not as source-of-truth state.
-
Read at session start
- If the snapshot exists, read it before taking action to recover intent, recent decisions, and proposed next steps.
-
Check staleness before relying on it
- Treat
updated_atas advisory only. - Compare snapshot recency against current date/time and recent git activity (
git log, current branch, PR/check status) before trusting operational details.
- Treat
-
Prefer authoritative state for execution
- Use git/GitHub state (working tree, commits, PR checks, mergeability) as the authoritative source for current truth.
- Use snapshot content mainly for context, rationale, and handoff continuity.
-
Refresh after milestones
- Update the snapshot after major transitions (merge/rebase, conflict resolution, docs/version bumps, CI/check changes, review-state changes) and before ending a session.
When implementing work tied to an issue/PR, proactively detect scope creep and preserve reviewable units.
- Never create commits or push branch updates unless the user explicitly asks for a commit/push in the current session.
- Staging and local validation are allowed as preparation, but commit/push is opt-in only.
-
Detect scope creep early
- Treat newly identified, non-blocking improvements as potential follow-up scope, not automatic additions.
- Examples: adjacent hardening, separate automation, or unrelated workflow polish.
- Use an aggressive default: if a discovered change is not required for current acceptance criteria or to fix a blocking defect, classify it as out-of-scope.
-
Pause and classify discovered work
- If work is required to complete the current acceptance criteria or fix a blocking defect, keep it in scope.
- If work is useful but not required, mark it out-of-scope for the current issue/PR.
-
Ask before expanding scope
- Present out-of-scope work to the user and ask whether to expand current scope or defer.
- Default recommendation: keep the current PR focused and defer non-blocking work.
- If the user explicitly approves scope expansion, treat that approval as authoritative and proceed with the accepted expansion.
-
If deferring, open a follow-up issue
- Create a new issue with clear summary, rationale, and acceptance criteria.
- Link it to the relevant parent tracker and cross-reference the current issue/PR.
-
Keep current PR focused and document the choice
- Continue implementing only the scoped work in the current branch/PR unless user approves expansion.
- Add a short PR comment and/or PR body note stating what was deferred and where it will be tracked.
- Update planning docs when they maintain ordered execution lists.
Follow these industry-standard Go practices:
-
Code Organization
- Use meaningful package names that reflect their purpose
- Keep packages focused and cohesive
- Avoid circular dependencies
-
Naming Conventions
- Use
camelCasefor private identifiers - Use
PascalCasefor exported identifiers - Use descriptive names that clearly indicate purpose
- Avoid stuttering (e.g.,
http.HTTPServer→http.Server)
- Use
-
Error Handling
- Always check and handle errors explicitly
- Don't panic in library code (use errors instead)
- Wrap errors with context using
fmt.Errorfwith%wverb - Return errors as the last return value
-
Formatting and Style
- Use
gofmtorgoimportsto format code - Follow the Effective Go guidelines
- Use
golintandgo vetto catch common issues
- Use
-
Concurrency
- Use channels for communication between goroutines
- Protect shared state with mutexes or use channels
- Always document when a function starts a goroutine
- Use context for cancellation and timeouts
-
Testing
- Write table-driven tests
- Use meaningful test names (e.g.,
TestFunctionName_Scenario) - Test both success and error paths
- Use subtests for related test cases
// Good: Idiomatic Go
func ProcessUser(ctx context.Context, userID string) (*User, error) {
if userID == "" {
return nil, fmt.Errorf("userID cannot be empty")
}
user, err := fetchUserFromDB(ctx, userID)
if err != nil {
return nil, fmt.Errorf("failed to fetch user %s: %w", userID, err)
}
return user, nil
}Follow these industry-standard Bash practices:
-
Shebang
- Use
#! /usr/bin/env bashfor portability - Note the space after
#!as per project convention
- Use
-
Error Handling
- Use
set -eto exit on error - Use
set -uto treat unset variables as errors (optional but recommended) - Use
set -o pipefailto catch errors in pipes - Trap errors for cleanup:
trap 'cleanup' EXIT ERR
- Use
-
Variable Quoting
- Always quote variables:
"$variable"not$variable - Use
"${variable}"for clarity in complex expressions - Quote command substitutions:
"$(command)"
- Always quote variables:
-
Validation and Checks
- Validate inputs at the start of the script
- Check file existence before operations
- Validate expected formats (e.g., semver, dates)
-
Functions
- Use functions for reusable code blocks
- Document function purpose with comments
- Declare local variables with
local
-
Logging and Debugging
- Use
echofor user-facing messages - Direct errors to stderr:
echo "Error: message" >&2 - Exit with non-zero status on errors
- Use
#! /usr/bin/env bash
# Script description: Brief description of what this script does
set -e # Exit on error
set -o pipefail # Catch errors in pipes
# Constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly CONFIG_FILE="${SCRIPT_DIR}/config.yaml"
# Validate prerequisites
if [ ! -f "$CONFIG_FILE" ]; then
echo "Error: Config file not found at $CONFIG_FILE" >&2
exit 1
fi
# Function with proper structure
process_data() {
local input_file="$1"
local output_file="$2"
if [ -z "$input_file" ] || [ -z "$output_file" ]; then
echo "Error: Missing required arguments" >&2
return 1
fi
# Process the data
cat "$input_file" > "$output_file"
}
# Main logic
main() {
local input="${1:-}"
if [ -z "$input" ]; then
echo "Usage: $0 <input_file>" >&2
exit 1
fi
process_data "$input" "output.txt"
echo "Processing complete"
}
# Run main with all arguments
main "$@"- Google Shell Style Guide
- ShellCheck for static analysis
-
Comments
- Write clear, concise comments explaining "why", not "what"
- Keep comments up-to-date with code changes
- Use complete sentences with proper punctuation
-
Function/Method Documentation
- Document all exported functions/methods
- Include parameters, return values, and error conditions
- Provide usage examples for complex functions
-
README Files
- Include project overview and purpose
- Document installation and setup steps
- Provide usage examples
- List prerequisites and dependencies
-
CHANGELOG
- Follow Keep a Changelog format
- Use semantic versioning
- Categorize changes: Added, Changed, Deprecated, Removed, Fixed, Security
// ProcessPayment processes a payment transaction for the given amount.
// It validates the payment details, charges the payment method, and
// returns a transaction ID on success.
//
// Parameters:
// - ctx: Context for cancellation and timeouts
// - paymentID: Unique identifier for the payment
// - amount: Payment amount in cents (must be positive)
//
// Returns:
// - transactionID: Unique transaction identifier
// - error: nil on success, or an error describing the failure
//
// Example:
// txID, err := ProcessPayment(ctx, "pay_123", 1000)
// if err != nil {
// log.Fatalf("Payment failed: %v", err)
// }
func ProcessPayment(ctx context.Context, paymentID string, amount int) (string, error) {
// Implementation
}-
Test Coverage
- Aim for at least 80% code coverage
- Test both happy path and error cases
- Include edge cases and boundary conditions
-
Test Organization
- Use table-driven tests for multiple scenarios
- Group related tests using subtests
- Keep tests independent and isolated
-
Test Naming
- Use descriptive test names:
TestFunction_Scenario_ExpectedBehavior - Example:
TestProcessPayment_InvalidAmount_ReturnsError
- Use descriptive test names:
-
Mocking and Fixtures
- Use interfaces for dependencies to enable mocking
- Keep test fixtures minimal and focused
- Clean up resources after tests
-
Integration Tests
- Separate unit tests from integration tests
- Use build tags for integration tests:
// +build integration - Document setup requirements for integration tests
func TestProcessUser_ValidInput_ReturnsUser(t *testing.T) {
tests := []struct {
name string
userID string
want *User
wantErr bool
}{
{
name: "valid user ID",
userID: "user123",
want: &User{ID: "user123", Name: "John"},
wantErr: false,
},
{
name: "empty user ID",
userID: "",
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ProcessUser(context.Background(), tt.userID)
if (err != nil) != tt.wantErr {
t.Errorf("ProcessUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ProcessUser() = %v, want %v", got, tt.want)
}
})
}
}Follow Semantic Versioning 2.0.0:
- MAJOR version: Incompatible API changes
- MINOR version: New functionality, backward compatible
- PATCH version: Backward compatible bug fixes
-
Determine Version Type
- Breaking changes → MAJOR bump
- New features → MINOR bump
- Bug fixes → PATCH bump
- Dependency updates (Dependabot) → PATCH bump
-
Update Files
- Update version in
resources/version.yaml(and mirrorbsctl/static/resources/constants.yamlduring transition) - Add CHANGELOG entry with date and changes
- Follow the CHANGELOG format
- Update version in
-
CHANGELOG Format
## [X.Y.Z] - YYYY-MM-DD ### Added - New feature description ### Changed - Change description ### Fixed - Bug fix description
-
Automated Bumping
- Dependabot PRs are automatically bumped via workflow
- Manual PRs require manual version bump
- Every MR/PR/changeset should include a version bump and matching CHANGELOG entry unless explicitly exempted
- Always bump version before merging
## [0.1.5] - 2026-01-17
### Changed
- Bump actions/checkout from v4 to v6
- Updated dependency parsing logic for better robustness
### Fixed
- Fixed CHANGELOG formatting for proper entry separation-
Tools
gofmt/goimports: Formattinggolint: Style checksgo vet: Static analysisgolangci-lint: Comprehensive linting (recommended)staticcheck: Advanced static analysis
-
Running Linters
# Format code gofmt -w . # Run go vet go vet ./... # Run golangci-lint golangci-lint run
-
CI Integration
- All linters must pass in CI
- Configure linters in
.golangci.yml - Fix all warnings before merging
-
Tools
shellcheck: Shell script static analysisshfmt: Shell script formatter
-
Running Linters
# Check scripts shellcheck script.sh # Format scripts shfmt -w script.sh
-
Common Issues to Avoid
- Unquoted variables
- Using
==in[ ](use=or[[instead) - Not checking command exit codes
- Missing error handling
-
Pre-commit Checks
- Format code before committing
- Run relevant linters
- Ensure tests pass locally
-
Code Review
- Address all review comments
- Update based on feedback
- Ensure CI passes before requesting review
-
Continuous Improvement
- Refactor complex code
- Update documentation
- Add tests for bug fixes
- Go by Example
- Advanced Bash-Scripting Guide
- The Art of Command Line
- Keep a Changelog
- Semantic Versioning
Note: This document is a living standard and will be updated as the project evolves. All contributors should follow these guidelines to maintain code quality and consistency.