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
- Merge Conflict Resolution
- Response Option Framing
- 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.
- If a user states they always handle commit/push themselves, treat that as standing preference and never commit/push unless explicitly asked in that session.
- Default behavior: do not commit or push unless asked; when work appears ready to commit, proactively propose a commit message draft for the user.
-
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.
When rebasing/merging long-lived branches, preserve mainline freshness and avoid silent regressions.
-
Never resolve by downgrading
- Prefer newer dependency/tool versions from
mainunless a documented compatibility, security, compliance, or operational-support issue requires pinning lower. - Treat version decreases (actions, language/toolchain, module/dependency, CLI semver) as regressions by default.
- Prefer newer dependency/tool versions from
-
Keep and extend changelog history
- Never delete existing
mainchangelog entries during conflict resolution. - Keep all existing released entries intact and add branch-relevant changes to the current branch/version entry per project bump policy.
- Never delete existing
-
Protect release/version files
- Resolve
resources/version.yamlandbsctl/static/resources/constants.yamlto at least the latest upstream version; when conflicts occur, bump forward from upstream as needed instead of reverting to an older value. - Do not reduce version values during conflict resolution.
- Resolve
-
Verify semantic parity after conflict resolution
- Compare rebased branch vs
origin/mainand verify there are no unintended reversions in pinned versions, toolchain levels, or workflow actions. - Run targeted checks affected by conflicts (for example docs/version checks when
CHANGELOG.mdor version files conflict).
- Compare rebased branch vs
-
Document any intentional downgrade
- If a lower version is intentionally kept for compatibility, record the reason in the PR and changelog entry so reviewers can validate the tradeoff.
When presenting implementation options, make tradeoffs explicit and easy to choose.
-
When a minimal path is offered, expose deeper options
- If you present a "minimal" approach, also offer to provide "recommended" and "full" variants.
- Keep minimal scoped to acceptance criteria and immediate unblock.
-
Use a consistent option ladder
minimal: least change to satisfy requirements safely.recommended: balanced durability/maintainability with moderate scope.full: comprehensive hardening/completeness with larger scope.
-
State scope and impact per option
- Include what changes, expected risk/noise, and validation depth.
- Call out when recommended/full would be scope expansion and require explicit user approval.
-
Respect explicit user direction
- If the user picks minimal/recommended/full (or explicitly approves scope expansion), follow that direction as authoritative.
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 should include at least one version bump and matching CHANGELOG entry unless explicitly exempted
- Default to one bump per PR branch; follow-up commits should update the existing current-version entry rather than creating another version
- Only bump again on the same PR when explicitly requested by the user or required by release policy
- If a docs bump is already present on the branch, rerun docs-bump checks after each meaningful change to reduce missed changelog/version drift
- Always ensure the branch is bumped 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.