Thank you for your interest in contributing to Zeitgeist! This guide will help you get started.
- Development Setup
- Building and Testing
- Code Style
- Project Structure
- Adding New Features
- Testing Guidelines
- Pull Request Process
- Reporting Issues
- Zig 0.15.2 or later (see ziglang.org)
- Git
git clone https://github.com/bkataru/zeitgeist
cd zeitgeist
# Debug build (faster compilation, slower runtime)
zig build
# Release build (slower compilation, optimized runtime)
zig build -Doptimize=ReleaseFastThe executable will be available at zig-out/bin/zeitgeist (and zig-out/bin/zg as a short alias).
# Debug build
zig build
# Release build (optimized)
zig build -Doptimize=ReleaseFast
# Install to a prefix (e.g., ~/.local)
zig build install --prefix ~/.local# Run all unit tests (247 tests)
zig build test --summary all
# Run integration tests (30 tests)
zig build integration --summary all
# Run both test suites
zig build test --summary all && zig build integration --summary all
# Run specific test by name
zig build test -- --test-filter="parse JSON"# Run directly via build system
zig build run -- [OPTIONS] [PATH]
# Run with verbose output
zig build run -- --verbose .
# Run with specific options
zig build run -- -s markdown -o output.md ./src
# Or use the built executable directly
./zig-out/bin/zeitgeist [OPTIONS] [PATH]- Functions and variables:
snake_case - Types and structs:
PascalCase - Constants:
SCREAMING_SNAKE_CASEorsnake_case(context-dependent) - File names:
snake_case.zig
- Follow Zig standard library conventions
- Add doc comments (
///) for all public functions and types - Keep functions focused and small (< 50 lines preferred)
- Handle errors explicitly - never discard errors with
_ - Use
deferfor cleanup to ensure resources are freed - Prefer slices over pointers when possible
- Use
std.mem.Allocatorfor all dynamic allocations
/// Processes a file and returns its token count.
/// Returns an error if the file cannot be read.
pub fn processFile(allocator: std.mem.Allocator, path: []const u8) !usize {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const content = try file.readToEndAlloc(allocator, max_file_size);
defer allocator.free(content);
return countTokens(content);
}Note: The build produces two executables:
zeitgeistandzg(short alias). Both are identical in functionality.
zeitgeist/
├── src/
│ ├── cli/ # Command-line interface
│ │ └── main.zig # CLI entry point (~2600 lines)
│ └── lib/ # Core library modules
│ ├── lib.zig # Library exports (public API)
│ ├── config.zig # Core Config struct
│ ├── config_file.zig # Config file parsing (JSON/YAML/TOML)
│ ├── scanner.zig # Directory scanning, tree building
│ ├── processor.zig # File processing, encoding detection
│ ├── output.zig # Output generation (XML/MD/JSON/Text)
│ ├── filter.zig # Glob patterns, gitignore parsing
│ ├── git.zig # Git integration (sorting, diffs, logs)
│ ├── security.zig # Secret/API key scanning
│ ├── tokenizer.zig # Token counting (BPE approximation)
│ ├── chunker.zig # Output splitting for LLM limits
│ ├── category.zig # File categorization system
│ ├── template.zig # Custom output templates
│ ├── toml.zig # TOML parser
│ ├── compress.zig # Content compression
│ ├── notebook.zig # Jupyter notebook conversion
│ ├── remote.zig # Remote repository support
│ ├── updater.zig # Self-update functionality
│ ├── completions.zig # Shell completion generation
│ └── mcp.zig # MCP server implementation
├── tests/
│ └── integration.zig # Integration tests (30 tests)
├── schema/
│ └── zeitgeist.schema.json # JSON Schema for config validation
├── examples/
│ └── full-config.json # Complete example configuration
├── docs/
│ ├── CONFIGURATION.md # Configuration reference
│ ├── API.md # Library API documentation
│ └── references/ # Internal references
├── build.zig # Build configuration (creates zeitgeist + zg)
├── build.zig.zon # Package dependencies
├── CHANGELOG.md # Version history
├── LICENSE # MIT License
└── README.md # Project documentation
| Module | Responsibility |
|---|---|
scanner.zig |
Directory traversal, tree building, file discovery |
processor.zig |
File reading, encoding detection, content processing |
output.zig |
Format-specific output generation |
filter.zig |
Glob matching, gitignore parsing, include/exclude logic |
config_file.zig |
JSON, YAML, and TOML configuration parsing |
security.zig |
Regex-based secret detection |
remote.zig |
GitHub/GitLab/Bitbucket API integration |
- Open an issue first - Discuss the use case and proposed implementation
- Design the API - Consider how the feature fits with existing code
- Implement the feature in the appropriate module
- Add comprehensive tests (minimum 3-5 tests per feature)
- Update documentation:
- Add doc comments to new public APIs
- Update README.md if user-facing
- Update CHANGELOG.md with the changes
- Update schema if adding config options
- Test with real codebases before submitting
- Add the option to the
Optionsstruct insrc/cli/main.zig - Add argument parsing in the main argument loop
- Add help text in the
printHelpfunction - Wire the option to the appropriate
PackConfigfield - Add unit tests for argument parsing
- Add the field to the appropriate struct in
src/lib/config_file.zig - Add parsing logic for JSON, YAML, and TOML formats
- Update
schema/zeitgeist.schema.json - Update
examples/full-config.json - Add tests for each config format
Add unit tests at the bottom of source files using Zig's built-in test blocks:
test "myFunction handles empty input" {
const allocator = std.testing.allocator;
const result = try myFunction(allocator, "");
defer allocator.free(result);
try std.testing.expectEqual(@as(usize, 0), result.len);
}
test "myFunction handles unicode" {
const allocator = std.testing.allocator;
const result = try myFunction(allocator, "こんにちは");
defer allocator.free(result);
try std.testing.expect(result.len > 0);
}For end-to-end testing, add to tests/integration.zig:
test "pack directory with custom config" {
const allocator = std.testing.allocator;
// Create test directory structure
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
// Write test files
try tmp.dir.writeFile("test.zig", "const x = 1;");
// Run the packer
var packer = try Packer.init(allocator, .{});
defer packer.deinit();
const result = try packer.pack(tmp.dir.path);
defer allocator.free(result);
// Verify output
try std.testing.expect(std.mem.indexOf(u8, result, "test.zig") != null);
}- New features: Minimum 3-5 tests covering normal, edge, and error cases
- Bug fixes: At least 1 test that reproduces and verifies the fix
- Refactoring: Existing tests should continue to pass
- Fork the repository and create your feature branch from
main - Ensure all tests pass:
zig build test --summary all && zig build integration --summary all
- Update documentation as needed
- Update CHANGELOG.md with your changes under
[Unreleased]
Include in your PR description:
- Summary: What does this PR do?
- Motivation: Why is this change needed?
- Testing: How was this tested?
- Breaking changes: Any breaking changes? (for library API)
type: short description (under 72 chars)
Longer description if needed. Explain the what and why,
not the how (the code shows that).
Fixes #123
Types: feat, fix, docs, refactor, test, chore
Examples:
feat: add TOML config supportfix: handle UTF-16 BOM in file detectiondocs: update README with new CLI optionsrefactor: simplify token counting logic
- Search existing issues to avoid duplicates
- Check if the issue persists with the latest version
- Try to reproduce with a minimal example
**Environment**
- OS: [e.g., Windows 11, macOS 14, Ubuntu 24.04]
- Zig version: [output of `zig version`]
- Zeitgeist version: [output of `zeitgeist --version`]
**Description**
A clear description of the bug or feature request.
**Steps to Reproduce**
1. Run `zeitgeist ...`
2. See error
**Expected Behavior**
What you expected to happen.
**Actual Behavior**
What actually happened.
**Additional Context**
Error messages, stack traces, screenshots, etc.- Be respectful and constructive in discussions
- Focus on the technical merits of contributions
- Help others learn and grow
- Assume good intentions
By contributing to Zeitgeist, you agree that your contributions will be licensed under the MIT License.
MIT License
Copyright (c) 2026 bkataru (Baalateja Kataru)
See the LICENSE file for details.
If you have questions about contributing:
- Check existing issues and discussions
- Open a new issue with the
questionlabel - Be specific about what you're trying to accomplish