Contributing to ztick
This guide explains how to contribute code, documentation, and bug reports to ztick.
Code of Conduct
Be respectful and constructive. We welcome contributions from everyone.
Getting Started
1. Fork and Clone
git clone https://github.com/yourusername/ztick.git
cd ztick2. Create a Branch
Use descriptive branch names:
git checkout -b feature/add-http-runner
# or
git checkout -b fix/protocol-parsing-bug
# or
git checkout -b docs/api-reference3. Make Changes
Follow the architecture and conventions described in Architecture.
4. Run Tests
Ensure all tests pass:
zig build test5. Format Code
Enforce code style:
zig fmt .6. Commit
Write clear, concise commit messages:
git commit -m "feat: implement HTTP runner adapter
- Add HTTP client interface
- Support webhook execution
- Add tests for HTTP parsing and requests"7. Push and Open PR
git push origin feature/add-http-runnerThen open a pull request on GitHub with a clear description of your changes.
Code Style Guide
Zig Conventions
Naming
- Variables and functions:
snake_case - Types and structs:
PascalCase - Constants:
SCREAMING_SNAKE_CASE
const max_jobs = 1000; // constant const Job = struct { ... }; // type fn process_job() void { ... } // function var current_job: Job = undefined; // variable- Variables and functions:
Functions
- Keep functions short (< 50 lines)
- Use descriptive names that explain intent
- Document public functions with doc comments
/// Processes a job and returns the execution result. /// Caller owns the returned memory. pub fn process(allocator: std.mem.Allocator, job: Job) !?Execution { // implementation }Error Handling
- Use error unions (
!Type) for fallible operations - Propagate errors with
trykeyword - Avoid panic in library code
// Good const job = try scheduler.get_job(id); // Bad const job = scheduler.get_job(id) orelse unreachable;- Use error unions (
Memory Management
- Pass allocator as parameter to all functions that allocate
- Document who owns returned memory
- Use
deferfor cleanup
pub fn create_job(allocator: std.mem.Allocator, id: []const u8) !Job { const owned_id = try allocator.dupe(u8, id); defer allocator.free(owned_id); // Use owned_id }
Architecture Compliance
Dependency Direction
- Domain → only uses Zig stdlib
- Application → uses Domain + stdlib
- Infrastructure → uses Domain + Application + stdlib
- Interfaces → uses all layers
Check your imports:
// In domain/job.zig const std = @import("std"); // ✓ OK const application = @import("../application.zig"); // ✗ Violates architectureTesting
- Co-locate unit tests with implementation
- Use
testblocks in the same file - Aim for 80%+ coverage
pub const Job = struct { identifier: []const u8, execution: i64, }; test "job creation" { const job = Job{ .identifier = "test", .execution = 1000 }; try std.testing.expect(job.execution == 1000); }Error Messages
- Be specific: “invalid job identifier” not “invalid”
- Lowercase start: “expected whitespace, found EOF”
Documentation
Comments
- Explain “why” not “what”
- Use
///for public API documentation - Use
//for internal logic
/// Schedules a job for execution at the given timestamp. /// Returns an error if the job identifier is already in use. pub fn schedule(self: *Scheduler, job: Job) !void { // Check if job already exists if (self.jobs.get(job.identifier)) |_| { return error.DuplicateIdentifier; } // ... }Commit Messages
- Use conventional commits:
feat:,fix:,docs:,test:,refactor: - First line: <= 70 characters
- Include rationale in body
fix: prevent jobs from executing before scheduled time Previously, jobs could execute 1 tick early if scheduled exactly at the evaluation boundary. This was because the comparison used `<=` instead of `<`. Fixes #123- Use conventional commits:
Before Submitting a PR
- All tests pass:
zig build test - Code is formatted:
zig fmt . && git diff --check - Commit messages follow convention
- Documentation updated (if user-facing change)
- Feature branch is up-to-date with
main
Review Process
Automated checks
- CI runs tests and formatters
- All checks must pass before review
Code review
- At least one maintainer reviews changes
- Feedback on architecture, style, correctness
- Changes requested → update branch, push, request re-review
Merge
- Approved changes are merged with squash or rebase
- Branch is deleted
Types of Contributions
Bug Fixes
Start with a failing test:
test "job should not execute before scheduled time" {
var scheduler = try Scheduler.init(allocator);
try scheduler.handle_query(Request{
.instruction = .{ .set = .{ .identifier = "test", .execution = 1000 } }
});
try scheduler.tick(999); // One tick before
const job = scheduler.job_storage.get("test");
try std.testing.expectEqual(JobStatus.planned, job.?.status);
}Then fix the implementation to make the test pass.
New Features
- Update domain types if needed
- Implement application logic
- Add infrastructure adapters
- Wire into interfaces
- Include tests at each layer
- Update user documentation
Documentation
- Fix typos and clarify explanations
- Add missing examples
- Update references to match code
- Add architecture notes for future contributors
Questions?
- Check existing issues and PRs
- Ask in discussions
- Review architecture documentation
License
By contributing, you agree that your contributions are licensed under the same license as the project.