* docs(ci): add CI workflow map and cross-links * chore(ci): harden workflow determinism and safety * chore(ci): address workflow review feedback * style(ci): normalize workflow and ci-map formatting
266 lines
7.7 KiB
Markdown
266 lines
7.7 KiB
Markdown
# Contributing to ZeroClaw
|
|
|
|
Thanks for your interest in contributing to ZeroClaw! This guide will help you get started.
|
|
|
|
## Development Setup
|
|
|
|
```bash
|
|
# Clone the repo
|
|
git clone https://github.com/theonlyhennygod/zeroclaw.git
|
|
cd zeroclaw
|
|
|
|
# Enable the pre-push hook (runs fmt, clippy, tests before every push)
|
|
git config core.hooksPath .githooks
|
|
|
|
# Build
|
|
cargo build
|
|
|
|
# Run tests (all must pass)
|
|
cargo test
|
|
|
|
# Format & lint (must pass before PR)
|
|
cargo fmt && cargo clippy -- -D warnings
|
|
|
|
# Release build (~3.4MB)
|
|
cargo build --release
|
|
```
|
|
|
|
### Pre-push hook
|
|
|
|
The repo includes a pre-push hook in `.githooks/` that enforces `cargo fmt --check`, `cargo clippy -- -D warnings`, and `cargo test` before every push. Enable it with `git config core.hooksPath .githooks`.
|
|
|
|
To skip it during rapid iteration:
|
|
|
|
```bash
|
|
git push --no-verify
|
|
```
|
|
|
|
> **Note:** CI runs the same checks, so skipped hooks will be caught on the PR.
|
|
|
|
## High-Volume Collaboration Rules
|
|
|
|
When PR traffic is high (especially with AI-assisted contributions), these rules keep quality and throughput stable:
|
|
|
|
- **One concern per PR**: avoid mixing refactor + feature + infra in one change.
|
|
- **Small PRs first**: prefer PR size `XS/S/M`; split large work into stacked PRs.
|
|
- **Template is mandatory**: complete every section in `.github/pull_request_template.md`.
|
|
- **Explicit rollback**: every PR must include a fast rollback path.
|
|
- **Security-first review**: changes in `src/security/`, runtime, and CI need stricter validation.
|
|
|
|
Full maintainer workflow: [`docs/pr-workflow.md`](docs/pr-workflow.md).
|
|
CI workflow ownership and triage map: [`docs/ci-map.md`](docs/ci-map.md).
|
|
|
|
## Agent Collaboration Guidance
|
|
|
|
Agent-assisted contributions are welcome and treated as first-class contributions.
|
|
|
|
For smoother agent-to-agent and human-to-agent review:
|
|
|
|
- Keep PR summaries concrete (problem, change, non-goals).
|
|
- Include reproducible validation evidence (`fmt`, `clippy`, `test`, scenario checks).
|
|
- Add brief workflow notes when automation materially influenced design/code.
|
|
- Call out uncertainty and risky edges explicitly.
|
|
|
|
We do **not** require PRs to declare an AI-vs-human line ratio.
|
|
|
|
Agent implementation playbook lives in [`AGENTS.md`](AGENTS.md).
|
|
|
|
## Architecture: Trait-Based Pluggability
|
|
|
|
ZeroClaw's architecture is built on **traits** — every subsystem is swappable. This means contributing a new integration is as simple as implementing a trait and registering it in the factory function.
|
|
|
|
```
|
|
src/
|
|
├── providers/ # LLM backends → Provider trait
|
|
├── channels/ # Messaging → Channel trait
|
|
├── observability/ # Metrics/logging → Observer trait
|
|
├── runtime/ # Platform adapters → RuntimeAdapter trait
|
|
├── tools/ # Agent tools → Tool trait
|
|
├── memory/ # Persistence/brain → Memory trait
|
|
└── security/ # Sandboxing → SecurityPolicy
|
|
```
|
|
|
|
## How to Add a New Provider
|
|
|
|
Create `src/providers/your_provider.rs`:
|
|
|
|
```rust
|
|
use async_trait::async_trait;
|
|
use anyhow::Result;
|
|
use crate::providers::traits::Provider;
|
|
|
|
pub struct YourProvider {
|
|
api_key: String,
|
|
client: reqwest::Client,
|
|
}
|
|
|
|
impl YourProvider {
|
|
pub fn new(api_key: Option<&str>) -> Self {
|
|
Self {
|
|
api_key: api_key.unwrap_or_default().to_string(),
|
|
client: reqwest::Client::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Provider for YourProvider {
|
|
async fn chat(&self, message: &str, model: &str, temperature: f64) -> Result<String> {
|
|
// Your API call here
|
|
todo!()
|
|
}
|
|
}
|
|
```
|
|
|
|
Then register it in `src/providers/mod.rs`:
|
|
|
|
```rust
|
|
"your_provider" => Ok(Box::new(your_provider::YourProvider::new(api_key))),
|
|
```
|
|
|
|
## How to Add a New Channel
|
|
|
|
Create `src/channels/your_channel.rs`:
|
|
|
|
```rust
|
|
use async_trait::async_trait;
|
|
use anyhow::Result;
|
|
use tokio::sync::mpsc;
|
|
use crate::channels::traits::{Channel, ChannelMessage};
|
|
|
|
pub struct YourChannel { /* config fields */ }
|
|
|
|
#[async_trait]
|
|
impl Channel for YourChannel {
|
|
fn name(&self) -> &str { "your_channel" }
|
|
|
|
async fn send(&self, message: &str, recipient: &str) -> Result<()> {
|
|
// Send message via your platform
|
|
todo!()
|
|
}
|
|
|
|
async fn listen(&self, tx: mpsc::Sender<ChannelMessage>) -> Result<()> {
|
|
// Listen for incoming messages, forward to tx
|
|
todo!()
|
|
}
|
|
|
|
async fn health_check(&self) -> bool { true }
|
|
}
|
|
```
|
|
|
|
## How to Add a New Observer
|
|
|
|
Create `src/observability/your_observer.rs`:
|
|
|
|
```rust
|
|
use crate::observability::traits::{Observer, ObserverEvent, ObserverMetric};
|
|
|
|
pub struct YourObserver { /* client, config, etc. */ }
|
|
|
|
impl Observer for YourObserver {
|
|
fn record_event(&self, event: &ObserverEvent) {
|
|
// Push event to your backend
|
|
}
|
|
|
|
fn record_metric(&self, metric: &ObserverMetric) {
|
|
// Push metric to your backend
|
|
}
|
|
|
|
fn name(&self) -> &str { "your_observer" }
|
|
}
|
|
```
|
|
|
|
## How to Add a New Tool
|
|
|
|
Create `src/tools/your_tool.rs`:
|
|
|
|
```rust
|
|
use async_trait::async_trait;
|
|
use anyhow::Result;
|
|
use serde_json::{json, Value};
|
|
use crate::tools::traits::{Tool, ToolResult};
|
|
|
|
pub struct YourTool { /* security policy, config, etc. */ }
|
|
|
|
#[async_trait]
|
|
impl Tool for YourTool {
|
|
fn name(&self) -> &str { "your_tool" }
|
|
|
|
fn description(&self) -> &str { "Does something useful" }
|
|
|
|
fn parameters_schema(&self) -> Value {
|
|
json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"input": { "type": "string", "description": "The input" }
|
|
},
|
|
"required": ["input"]
|
|
})
|
|
}
|
|
|
|
async fn execute(&self, args: Value) -> Result<ToolResult> {
|
|
let input = args["input"].as_str()
|
|
.ok_or_else(|| anyhow::anyhow!("Missing 'input'"))?;
|
|
Ok(ToolResult {
|
|
success: true,
|
|
output: format!("Processed: {input}"),
|
|
error: None,
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
## Pull Request Checklist
|
|
|
|
- [ ] PR template sections are completed (including security + rollback)
|
|
- [ ] `cargo fmt --all -- --check` — code is formatted
|
|
- [ ] `cargo clippy --all-targets -- -D warnings` — no warnings
|
|
- [ ] `cargo test` — all 129+ tests pass
|
|
- [ ] New code has inline `#[cfg(test)]` tests
|
|
- [ ] No new dependencies unless absolutely necessary (we optimize for binary size)
|
|
- [ ] README updated if adding user-facing features
|
|
- [ ] Follows existing code patterns and conventions
|
|
|
|
## Commit Convention
|
|
|
|
We use [Conventional Commits](https://www.conventionalcommits.org/):
|
|
|
|
```
|
|
feat: add Anthropic provider
|
|
feat(provider): add Anthropic provider
|
|
fix: path traversal edge case with symlinks
|
|
docs: update contributing guide
|
|
test: add heartbeat unicode parsing tests
|
|
refactor: extract common security checks
|
|
chore: bump tokio to 1.43
|
|
```
|
|
|
|
Recommended scope keys in commit titles:
|
|
|
|
- `provider`, `channel`, `memory`, `security`, `runtime`, `ci`, `docs`, `tests`
|
|
|
|
## Code Style
|
|
|
|
- **Minimal dependencies** — every crate adds to binary size
|
|
- **Inline tests** — `#[cfg(test)] mod tests {}` at the bottom of each file
|
|
- **Trait-first** — define the trait, then implement
|
|
- **Security by default** — sandbox everything, allowlist, never blocklist
|
|
- **No unwrap in production code** — use `?`, `anyhow`, or `thiserror`
|
|
|
|
## Reporting Issues
|
|
|
|
- **Bugs**: Include OS, Rust version, steps to reproduce, expected vs actual
|
|
- **Features**: Describe the use case, propose which trait to extend
|
|
- **Security**: See [SECURITY.md](SECURITY.md) for responsible disclosure
|
|
|
|
## Maintainer Merge Policy
|
|
|
|
- Require passing `CI Required Gate` before merge.
|
|
- Require review approval for non-trivial changes.
|
|
- Require CODEOWNERS review for protected paths.
|
|
- Prefer squash merge with conventional commit title.
|
|
- Revert fast on regressions; re-land with tests.
|
|
|
|
## License
|
|
|
|
By contributing, you agree that your contributions will be licensed under the MIT License.
|