This document provides guidelines for AI agents working on the VFS repository, including upgrade policies, dependency management, and maintenance procedures.
- Development Guidelines
- CHANGELOG and PR Process
- Go Version Policy
- Dependency Upgrades
- GitHub Actions Maintenance
- Module Management
- All new code MUST have tests
- Aim for coverage as close to 100% as is practicable
- Minimum thresholds (enforced by CI):
- Total project coverage: 80%
- Package coverage: 63%
- File coverage: 52%
- Use
testify/suitefor organizing related tests- One test suite per major component/struct being tested
- Naming:
[ComponentName]TestSuite(e.g.,S3BackendTestSuite) - Use
SetupTest()andTearDownTest()for test isolation - Exception: testify/suite may not be suitable for benchmarks or simple unit tests
-
Prefer suite functions and facilities over external libraries
- Use suite's built-in methods for temp files, fixtures, etc.
- Use
s.Assert()ands.Require()from suite rather than standalone packages - Keep test dependencies minimal
-
Use table-driven tests where possible
- Slice of anonymous structs with
name, inputs, and expected outputs - Use descriptive test case names that explain the scenario
- Example:
tests := []struct { name string input string expectedOutput string expectedError string }{ { name: "Success case - normal operation", input: "valid", expectedOutput: "result", }, { name: "Error case - invalid input", input: "invalid", expectedError: "expected error message", }, } for _, tt := range tests { s.Run(tt.name, func() { result, err := functionUnderTest(tt.input) if tt.expectedError != "" { s.Require().Error(err) s.Assert().Contains(err.Error(), tt.expectedError) } else { s.Require().NoError(err) s.Assert().Equal(tt.expectedOutput, result) } }) }
- Slice of anonymous structs with
- Use build tag
vfsintegrationfor integration tests - Run with:
go test -tags=vfsintegration ./... - Integration tests may require external services or credentials
- All code MUST pass
golangci-lint - Run before committing:
make lint - Configuration:
.golangci.yml - Linter runs on all modules independently in CI
- Follow standard Go idioms and conventions
- Use
gofmtandgoimportsfor formatting - Exported functions and types must have documentation comments
- Keep functions focused and small
- Use early returns to reduce nesting
- Handle all errors explicitly - no silent failures
- Use wrapped errors with context:
fmt.Errorf("operation failed: %w", err) - Provide meaningful error messages with context
- Never ignore error return values
- Prefer small, focused interfaces over large ones
- Define interfaces in consuming packages, not implementing packages
- Use dependency injection for testability
- Group related functionality in packages
- Use subdirectories for different implementations
- Place mocks in
mocks/subdirectories - One package per major feature
- Use
mockeryv3 for generating mocks with EXPECT() pattern - No manual mocks or fakes (unless absolutely necessary)
- Prefer EXPECT() pattern for mock setup for better readability and type safety
- Place mocks in dedicated
mocks/subdirectories - Generate mocks with:
//go:generate mockery --name=InterfaceName --output=./mocks - Example EXPECT() usage:
mockClient := mocks.NewMockSQSClient(t) mockClient.EXPECT().ReceiveMessage(mock.Anything, mock.MatchedBy(func(input *sqs.ReceiveMessageInput) bool { return *input.QueueUrl == queueURL })).Return(&sqs.ReceiveMessageOutput{Messages: messages}, nil)
- All exported types, functions, and methods must have godoc comments
- Include usage examples for complex functionality
- Update README.md when adding new features
- Document breaking changes in CHANGELOG.md
- Implement graceful shutdown with timeouts
- Use context for cancellation throughout
- Prevent resource leaks with proper cleanup (use
defer) - Be mindful of goroutine lifecycle and synchronization
- Every PR requires an update to the
## [Unreleased]section of CHANGELOG.md - PRs are always submitted without a new version number
- Version numbers are added automatically when the PR is merged to main
The CHANGELOG follows Keep a Changelog format.
Standard Section Headings (case-insensitive):
### Added- New features or functionality### Changed- Changes to existing functionality (breaking changes only)### Removed- Removed features or functionality (breaking changes only)### Deprecated- Features marked for future removal### Security- Security-related updates or fixes### Fixed- Bug fixes
### Changedand### Removedsections are ONLY for breaking changes- MUST include the exact phrase "BREAKING CHANGE" in the description
- If a change is not breaking, use
### Added(for new features) or### Fixed(for bug fixes) - Example:
### Changed - **BREAKING CHANGE**: Modified API behavior to return errors instead of panicking
Changes determine the next semantic version:
- Major bump:
### Changedor### Removedsections with "BREAKING CHANGE" - Minor bump:
### Added,### Deprecated, or### Securitysections - Patch bump: Only
### Fixedsections
## [Unreleased]
### Added
- New S3 backend option for custom endpoint configuration
### Fixed
- Fixed file handle leak in os backend when operations fail
- Corrected path separator handling on Windows- Add changes under
## [Unreleased]section in CHANGELOG.md - Use appropriate section headings based on change type
- Do NOT add version numbers - these are added during the merge/release process
- Be clear and specific in change descriptions
- Mark breaking changes with "BREAKING CHANGE" text
VFS guarantees compatibility with the latest Go version minus 1 minor version.
Example: If Go 1.26.1 is the latest release, VFS supports:
- Go 1.26.x (latest minor version)
- Go 1.25.x (latest - 1 minor version)
The go.mod file specifies the older of the two supported versions (e.g., go 1.25.8), ensuring compatibility with both.
- Update
go.modin the root module to the latest Go version (e.g.,go 1.25.8) - Update all configuration files that reference Go versions
- Release the core VFS version
- Then update contrib modules (they import core, so core must be released first)
When updating Go versions, update these files:
go.mod- Main module Go version.golangci.yml-run.gofield.github/workflows/go.yml-matrix.go-versionarray.github/workflows/golangci-lint.yml-go-versionfield.github/workflows/codeql.yml-go-versionfield
contrib/lockfile/go.modcontrib/vfsevents/go.modcontrib/backend/dropbox/go.mod- Any other contrib modules
- Update dependencies regularly for security and bug fixes
- Test thoroughly after dependency updates
- Document breaking changes in CHANGELOG.md
# Update all dependencies
go get -u -t ./...
go mod tidy
# Update specific dependency
go get -u github.com/example/package@latestAll GitHub Actions must be pinned to specific commit SHAs for security and reproducibility.
Format: uses: owner/action@<sha> # <version-tag>
Example: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- Select the newest version that is at least 10 days old
- If the latest version is too recent (< 10 days), check for intermediate versions
- Allow time for community vetting of new releases
- Check for security advisories before updating
- Always verify release dates before updating
-
Find Available Versions
- Visit the action's repository releases page
- Example:
https://github.com/actions/checkout/releases - Check the CHANGELOG if available for release dates
-
Select Newest Eligible Version
- Identify all versions newer than current
- Find the newest version that is at least 10 days old
- If the latest is too recent, look for intermediate versions
- This allows time for community feedback and security review
-
Get the Commit SHA
- Navigate to the selected tag/release on GitHub
- Click on the commit hash to get the full 40-character SHA
- Copy the full SHA (not the abbreviated version)
-
Update the Workflow File
# Before uses: actions/checkout@old-sha # v6.0.1 # After uses: actions/checkout@new-sha # v6.0.2
-
Test the Workflow
- Create a PR to test the updated action
- Verify all workflows pass successfully
.github/workflows/go.yml- Main test workflow (multi-module, multi-OS, multi-Go-version).github/workflows/golangci-lint.yml- Linting workflow.github/workflows/codeql.yml- Security scanning.github/workflows/go-test-coverage.yml- Test coverage enforcement.github/workflows/ensure_changelog.yml- CHANGELOG validation
VFS uses a multi-module repository structure:
vfs/
├── go.mod # Core VFS module
├── contrib/
│ ├── lockfile/go.mod # Lockfile contrib module
│ ├── vfsevents/go.mod # Events contrib module
│ └── backend/
│ └── dropbox/go.mod # Dropbox backend contrib module
- Contrib modules depend on core VFS
- Core must be released before updating contrib module imports
- Each module has independent versioning
- Update and test core VFS module
- Create release tag for core (e.g.,
v7.14.0) - Update contrib modules to reference new core version
- Test contrib modules
- Create release tags for contrib modules (e.g.,
contrib/lockfile/v1.2.0)
# Run tests for all modules
make test
# Run linter for all modules
make lint
# Manual testing
for dir in $(find . -name go.mod -exec dirname {} \;); do
echo "Testing $dir"
(cd "$dir" && go test ./...)
doneThe CI workflows automatically detect all modules using:
- name: Find all modules
run: |
modules=$(find . -name go.mod -exec dirname {} \; | jq --raw-input --slurp --compact-output 'split("\n")[:-1]')
echo "modules=$modules" >> $GITHUB_OUTPUTThis ensures new modules are automatically tested without workflow updates.
- Go Versions: 1.25, 1.26 (latest-1 and latest)
- Operating Systems: ubuntu-latest, macos-latest, windows-latest
- Modules: All modules with
go.modfiles
Workflows use concurrency groups to prevent resource waste:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}- Configuration:
.golangci.yml - Go version must match latest supported version
- Runs on all modules independently
- Build tags:
vfsintegrationfor integration tests
Run the linter:
golangci-lint run --verbose --max-same-issues 0 ./...- Minimum coverage requirements defined in
.testcoverage.yml - Enforced via
go-test-coverageaction - Coverage reports generated for all modules
- CodeQL runs on all PRs and weekly schedule
- Scans for security vulnerabilities in Go code
- Requires
GH_CI_READsecret for private dependencies
- All actions pinned to commit SHAs (not tags)
- Regular review of action updates
- 10-day waiting period for new releases
- Check for Go version updates
- Review dependency updates (
go list -u -m all) - Check GitHub Actions for updates (>10 days old)
- Review security advisories
- Update Go to latest stable version
- Update all dependencies
- Review and update CI/CD workflows
- Update this AGENTS.md document
- Update CHANGELOG.md
- Run full test suite on all platforms
- Verify all modules build successfully
- Update version tags
- Create GitHub release with notes
- Create directory under
contrib/ - Initialize
go.modwith dependency on core VFS - Add README.md with usage documentation
- Implement required interfaces
- Add comprehensive tests
- CI will automatically detect and test the new module
cd contrib/yourmodule
go get github.com/c2fo/vfs/v7@v7.14.0
go mod tidy# With build tags
go test -tags=vfsintegration ./...
# Specific backend
cd backend/s3
go test -tags=vfsintegration ./...