Thank you for considering contributing to bucklezig! This document outlines how to contribute to the project.
- Code of Conduct
- Getting Started
- Development Workflow
- Audio Profile Guidelines
- Testing Requirements
- Code Style
- Submitting Changes
- Be respectful and constructive in all interactions
- Focus on what is best for the community and project
- Show empathy towards other contributors
- Accept constructive criticism gracefully
- Zig 0.11+: Install Zig
- Go 1.21+: Install Go
- Platform Requirements:
- macOS: Xcode Command Line Tools (
xcode-select --install) - Linux: ALSA development libraries (
libasound2-dev)
- macOS: Xcode Command Line Tools (
- Fork the repository on GitHub
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/bucklezig.git cd bucklezig - Create sound profiles (see Audio Profile Guidelines)
- Build the project:
make build
- Run tests:
make test
We follow Test-Driven Development (TDD):
- Red: Write a failing test for the new feature/bugfix
- Green: Write minimal code to make the test pass
- Refactor: Improve code quality while keeping tests green
public- Main development branch (default)- Feature branches:
feature/descriptive-name - Bugfix branches:
fix/issue-description
# Create a feature branch
git checkout -b feature/my-new-feature
# Make changes, write tests
# Ensure tests pass
make test
# Commit with descriptive message
git commit -am "Add feature: brief description"
# Push to your fork
git push origin feature/my-new-feature
# Open a Pull Request on GitHubIMPORTANT: Only use audio files you have legal rights to distribute. Acceptable sources:
- Public Domain: Freesound.org (CC0 license)
- Creative Commons: Verify license allows redistribution (CC-BY, CC-BY-SA)
- Self-recorded: Your own keyboard recordings
- Royalty-free libraries: Check licensing terms carefully
- ❌ Rip audio from commercial products
- ❌ Use copyrighted sounds without permission
- ❌ Submit profiles with unclear licensing
Each profile requires 256 WAV files in this structure:
profiles/
└── YourProfileName/
├── 1-down.wav # Key 1 press
├── 1-up.wav # Key 1 release
├── 2-down.wav
├── 2-up.wav
...
├── 128-down.wav
└── 128-up.wav
- Format: WAV (uncompressed PCM)
- Sample Rate: 44100 Hz or 48000 Hz
- Bit Depth: 16-bit or 24-bit
- Channels: Mono or Stereo
- File Size: Keep under 500KB per file for fast loading
When submitting a new profile:
-
Include a
LICENSE.txtin the profile directory stating:- Sound source/author
- License type (e.g., "CC0 Public Domain")
- Attribution requirements (if any)
-
In your PR description, include:
- Profile name and description
- Sound source URL
- License confirmation
- Recording setup (if original)
Example:
profiles/Cherry-MX-Blue/
├── LICENSE.txt # "Sounds by John Doe, CC0 Public Domain"
├── 1-down.wav
├── 1-up.wav
...
- Minimum: 80% overall coverage
- Critical paths: 100% coverage required
- Audio playback engine
- Profile loading/validation
- Keyboard capture
- CGO bridge functions
# Zig tests
cd zig && zig build test
# Go tests
cd go && go test ./...
# Integration tests
make test-integration
# Coverage report
make test-coverageZig Example:
test "audio engine loads valid WAV file" {
const allocator = std.testing.allocator;
const engine = try AudioEngine.init(allocator);
defer engine.deinit();
try engine.loadSound("test.wav");
try std.testing.expect(engine.isLoaded());
}Go Example:
func TestProfileSwitching(t *testing.T) {
model := initialModel()
model, _ = model.Update(tea.KeyMsg{Type: tea.KeyDown})
assert.Equal(t, 1, model.selectedProfile)
}- Formatting: Run
zig fmtbefore committing - Naming:
snake_casefor functions/variables - Error Handling: Use error unions, never ignore errors
- Documentation: Doc comments (
///) for public APIs - Memory: Explicit allocators, no hidden allocations
Example:
/// Loads a WAV file from disk into memory.
/// Returns error if file is invalid or memory allocation fails.
pub fn loadWav(allocator: Allocator, path: []const u8) !Sound {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
// ...
}- Formatting: Run
gofmtbefore committing - Linting: Ensure
golangci-lint runpasses (no warnings) - Naming:
camelCasefor unexported,PascalCasefor exported - Error Handling: Wrap errors with context using
fmt.Errorfwith%w - BubbleTea: Follow Model-Update-View separation
Example:
// LoadProfile loads a sound profile by name.
// Returns an error if the profile directory doesn't exist or is incomplete.
func LoadProfile(name string) (*Profile, error) {
path := filepath.Join("profiles", name)
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("profile not found: %w", err)
}
// ...
}- Keep C API surface minimal
- Use only C-compatible types (
int,char*) - Document thread safety requirements
- Target <1ms overhead per call
-
Update documentation if adding features
-
Add tests covering your changes
-
Run full test suite and ensure it passes
-
Update AGENTS.md if changing architecture
-
Write clear commit messages:
Add profile hot-swap without audio interruption - Implement lock-free buffer swapping - Add integration tests for profile switching - Update TUI to show switching status Closes #42 -
Open PR with:
- Clear title describing the change
- Detailed description of what and why
- Link to related issues
- Screenshots/demos if UI changes
Before requesting review, verify:
- All tests pass (
make test) - Code coverage meets 80% minimum
- Code is formatted (
zig fmt,gofmt) - Linter passes (
golangci-lint run) - Documentation updated
- Commit messages are descriptive
- No debug code or commented-out blocks
- Audio files have proper licensing (if applicable)
Changes must maintain or improve:
- Audio latency: <10ms from key event to sound
- Profile switching: <200ms
- Startup time: <500ms
- Memory usage: <100MB per profile
- CPU idle: <1%
If your change impacts performance, include benchmark results.
- Test with both Intel and Apple Silicon
- Verify IOKit keyboard capture works
- Check CoreAudio compatibility
- Test with ALSA and PulseAudio
- Verify evdev keyboard capture
- Test on multiple distributions (Ubuntu, Fedora, Arch)
- Bug reports: Open an issue with detailed reproduction steps
- Feature requests: Open an issue describing the use case
- Discussion: Start a GitHub Discussion
- Security issues: Email maintainer directly (see README)
This project was initially developed with assistance from:
- OpenCode by OpenCoder AI
- Claude Sonnet 4.5 by Anthropic
We welcome contributions from all developers! 🎉