Thank you for your interest in contributing to Axe! This document provides guidelines and information for contributors.
- Getting Started
- Development Setup
- Code Style and Standards
- Testing
- Submitting Changes
- Project Structure
- Design Principles
- Go 1.25.0 or later
- Git
- A text editor or IDE with Go support
- (Optional) Docker for container testing
- (Optional) golangci-lint for linting
- Fork the repository on GitHub
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/axe.git cd axe - Add the upstream repository:
git remote add upstream https://github.com/jrswab/axe.git
go build .This creates an axe binary in the current directory.
# Run all tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run tests verbosely
go test -v ./...
# Run specific package tests
go test ./internal/agent/Golden file tests capture expected CLI output. To update them after intentional changes:
UPDATE_GOLDEN=1 go test ./cmd/golangci-lint runConfiguration is in .golangci.yml.
- Follow Go conventions - Use
gofmt, follow effective Go practices - Write clear, self-documenting code - Prefer clarity over cleverness
- Keep functions small and focused - Each function should do one thing well
- Avoid global state - Pass dependencies explicitly
- Error messages should help users - Explain what went wrong and how to fix it
Resolution Order: Flags override TOML overrides environment variables override defaults. This pattern is used throughout the codebase.
Output:
- Clean output to stdout (safe to pipe)
- Debug information to stderr
- Use
--verboseflag for detailed logging - Use
--jsonflag for structured output
Exit Codes:
0- Success1- Runtime error2- Configuration error
Path Handling:
- Support tilde expansion (
~/path) - Support environment variable expansion (
$HOME/path,${VAR}/path) - Validate paths to prevent directory traversal
- Sandbox file operations to working directory
- Unit tests -
*_test.gofiles alongside implementation - Integration tests -
cmd/*_integration_test.gofor end-to-end testing - Smoke tests -
cmd/smoke_test.gofor real binary execution - Golden file tests -
cmd/golden_test.gofor CLI output validation
Table-Driven Tests:
func TestMyFunction(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "valid input",
input: "test",
want: "result",
},
{
name: "invalid input",
input: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := MyFunction(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("MyFunction() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("MyFunction() = %v, want %v", got, tt.want)
}
})
}
}Using Test Utilities:
// Setup temporary XDG directories
cleanup := testutil.SetupXDGDirs(t)
defer cleanup()
// Seed test agents
testutil.SeedFixtureAgents(t, "testdata/agents", xdg.GetConfigDir())
// Create mock LLM server
server := testutil.NewMockLLMServer(t, testutil.AnthropicResponse("test response"))
defer server.Close()Avoid Mocking When Possible:
Prefer using real implementations or test utilities over mocks. Use internal/testutil/mockserver.go for provider testing.
- Run tests:
go test ./... - Run linter:
golangci-lint run - Update documentation if you changed behavior
- Update CHANGELOG.md with your changes
- Ensure commits are clean and have descriptive messages
Follow conventional commit format:
type(scope): brief description
Longer explanation if needed.
Fixes #123
Types:
feat- New featurefix- Bug fixdocs- Documentation changestest- Test additions or changesrefactor- Code refactoringchore- Maintenance tasks
Examples:
feat(tool): add url_fetch tool with HTML stripping
fix(memory): prevent race condition in concurrent writes
docs(readme): update installation instructions
test(provider): add OpenAI error handling tests
-
Create a feature branch:
git checkout -b feature/my-feature
-
Make your changes and commit them
-
Push to your fork:
git push origin feature/my-feature
-
Open a Pull Request on GitHub
-
Respond to review feedback - maintainers may request changes
-
Once approved, your PR will be merged
- One feature per PR - Keep changes focused
- Include tests for new functionality
- Update documentation as needed
- Ensure CI passes - all tests and linting must pass
- Write a clear description - explain what and why
axe/
├── cmd/ # CLI commands
│ ├── run.go # Agent execution
│ ├── agents.go # Agent management
│ ├── config.go # Configuration
│ ├── gc.go # Garbage collection
│ └── testdata/ # Test fixtures
├── internal/
│ ├── agent/ # Agent configuration
│ ├── config/ # Global configuration
│ ├── envinterp/ # Environment variable expansion
│ ├── mcpclient/ # MCP client
│ ├── memory/ # Persistent memory
│ ├── provider/ # LLM providers
│ ├── refusal/ # Refusal detection
│ ├── resolve/ # Context resolution
│ ├── testutil/ # Test utilities
│ ├── tool/ # Built-in tools
│ ├── toolname/ # Tool constants
│ └── xdg/ # XDG directories
├── docs/
│ ├── design/ # Design documents
│ └── plans/ # Implementation plans
├── examples/ # Example agents
└── skills/ # Embedded skills
main.go- Application entry pointcmd/root.go- Root command and error handlinginternal/provider/provider.go- Provider interfaceinternal/tool/registry.go- Tool registryinternal/agent/agent.go- Agent configuration
- Do one thing well - Each agent is single-purpose
- Compose with standard tools - Pipes, cron, git hooks
- Clean stdout - Output is safe to pipe
- Meaningful exit codes - 0 for success, 1 for runtime error, 2 for config error
- Small context windows - Each agent gets only what it needs
- Opaque sub-agents - Parents only see final results, not internals
- Focused skills - SKILL.md files provide targeted instructions
- TOML-based - Agent definitions are declarative
- No source changes - Users never touch Go code to create agents
- Version controllable - Configurations can be committed to git
- Table-driven tests - Consistent test structure
- Minimal mocking - Prefer real implementations
- Integration tests - Test end-to-end behavior
- Golden files - Capture expected CLI output
- Path sandboxing - File operations restricted to working directory
- Symlink validation - Prevent escaping working directory
- No arbitrary execution - Except explicit
run_commandtool
- Create
internal/tool/my_tool.go - Define tool entry function returning
DefinitionandExecutor - Implement executor with
ExecuteContextand arguments - Add constant to
internal/toolname/toolname.go - Register in
internal/tool/registry.goRegisterAll() - Add to
ValidNames()ininternal/toolname/toolname.go - Write tests in
internal/tool/my_tool_test.go - Add integration test in
cmd/run_integration_test.go - Update documentation
- Create
internal/provider/myprovider.go - Implement
Providerinterface withSend()method - Implement request/response conversion
- Implement error handling and categorization
- Add constructor (e.g.,
NewMyProvider()) - Register in
internal/provider/registry.goNew() - Add to
Supported()function - Write tests in
internal/provider/myprovider_test.go - Add integration test in
cmd/run_integration_test.go - Update documentation
- Create
cmd/mycommand.go - Define Cobra command with flags
- Implement command logic
- Add to root command in
init()function - Write tests in
cmd/mycommand_test.go - Add smoke test in
cmd/smoke_test.go - Create golden files if applicable
- Update documentation
- Issues - Open an issue on GitHub for bugs or feature requests
- Discussions - Use GitHub Discussions for questions
- Documentation - Check
docs/directory for design documents
- Be respectful and inclusive
- Focus on constructive feedback
- Help others learn and grow
- Assume good intentions
By contributing to Axe, you agree that your contributions will be licensed under the Apache-2.0 License.