Skillrust

Zgrok Developer Skill

Develop zgrok tunnel service following project conventions, issue specs, and Rust best practices. Use when implementing features, fixing bugs, or reviewing code in this repository.

View Source
SKILL.md
---
name: zgrok-developer
description: Develop zgrok tunnel service following project conventions, issue specs, and Rust best practices. Use when implementing features, fixing bugs, or reviewing code in this repository.
---

# zgrok Developer Skill

## ABSOLUTE REQUIREMENTS

**These are non-negotiable. Violating these is a failure condition.**

### No Tutorial Comments

NEVER add comments explaining what code does. Code must be self-documenting.

Allowed comments:
- `///` doc comments for public API
- `// TODO:` or `// FIXME:`
- `// SAFETY:` for unsafe blocks
- Brief "why" explanations for non-obvious decisions

Forbidden:
- `// Initialize the counter`
- `// Loop through items`
- `// Check if value is null`
- Any comment that restates what the code does

### Branch Workflow (Mandatory)

For EVERY issue:

1. `git checkout main && git pull origin main`
2. `git checkout -b feat/ZGROK-XXX-description`
3. Implement all acceptance criteria
4. `cargo fmt && cargo clippy -- -D warnings && cargo test`
5. `git add -A && git commit -m "feat(component): ZGROK-XXX - title"`
6. `git push -u origin feat/ZGROK-XXX-description`
7. `gh pr create --fill`
8. Self-review the diff
9. `gh pr merge --squash --delete-branch`
10. `git checkout main && git pull origin main`
11. Report completion, await next issue

**NEVER leave PRs open. NEVER work multiple issues. NEVER skip steps.**

## Overview

zgrok is a self-hosted ngrok alternative. This skill encodes project-specific knowledge for effective development.

## Issue-Driven Development

All work traces to issues in `.github/issues/`. Each issue contains:

- **Acceptance Criteria**: Testable Given/When/Then statements
- **State Machines**: For stateful components (connections, streams)
- **Technical Context**: Exact crates, files, data structures to use
- **Dependencies**: What must exist before this can be implemented

### Reading an Issue

```bash
cat .github/issues/stories/protocol/ZGROK-010-frame-types.json | jq
```

### Checking Dependencies

```bash
cat .github/issues/_index.json | jq '.dependency_graph["ZGROK-012"]'
```

## Architecture Principles

1. **Async-first**: Everything uses Tokio. No blocking in async context.
2. **Protocol-agnostic core**: The multiplexer works over any `AsyncRead + AsyncWrite`.
3. **Graceful degradation**: Connection loss → reconnect, not crash.
4. **Observable**: Every component emits tracing spans and metrics.

## Code Patterns

### Error Handling

```rust
// Library crates: use thiserror
#[derive(Debug, thiserror::Error)]
pub enum ProtocolError {
    #[error("invalid frame type: {0}")]
    InvalidFrameType(u8),
    #[error("stream {0} not found")]
    StreamNotFound(u32),
    #[error("io error: {0}")]
    Io(#[from] std::io::Error),
}

// Binary crates: use anyhow
fn main() -> anyhow::Result<()> {
    // ...
}
```

### Tracing

```rust
use tracing::{debug, info, instrument, warn};

#[instrument(skip(stream), fields(stream_id = %stream.id()))]
async fn handle_stream(stream: Stream) -> Result<(), Error> {
    info!("processing stream");
    // ...
}
```

### State Machines

Implement exactly as specified in issue `state_machine` field:

```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamState {
    Open,
    HalfClosedLocal,
    HalfClosedRemote,
    Closed,
}

impl StreamState {
    pub fn can_send(&self) -> bool {
        matches!(self, Self::Open | Self::HalfClosedRemote)
    }
    
    pub fn can_recv(&self) -> bool {
        matches!(self, Self::Open | Self::HalfClosedLocal)
    }
}
```

## Testing Strategy

1. **Unit tests**: Per-function, alongside code
2. **Property tests**: Protocol codec with `proptest`
3. **Integration tests**: Full tunnel flow in `tests/`

```rust
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_frame_roundtrip() {
        // Maps to AC: Given valid frame, When encoded then decoded, Then equals original
        let frame = Frame::Data { stream_id: 1, payload: b"hello".to_vec() };
        let encoded = frame.encode();
        let decoded = Frame::decode(&encoded).unwrap();
        assert_eq!(frame, decoded);
    }
}
```

## File Organization

```
crates/
├── zgrok-protocol/src/
│   ├── lib.rs          # Public API, re-exports
│   ├── frame.rs        # Frame types and codec
│   ├── codec.rs        # Tokio codec impl
│   ├── mux.rs          # Stream multiplexer
│   └── error.rs        # Protocol errors
├── zgrok-agent/src/
│   ├── main.rs         # CLI entry
│   ├── cli.rs          # Clap definitions
│   ├── tunnel.rs       # Connection management
│   ├── forwarder.rs    # Local HTTP forwarding
│   └── tui/            # Terminal UI
└── zgrok-edge/src/
    ├── main.rs
    ├── acceptor.rs     # Agent connection handling
    ├── router.rs       # Subdomain routing
    └── bridge.rs       # HTTP-to-tunnel bridging
```

## Commit Convention

```
feat(protocol): ZGROK-010 - define frame types and binary format
fix(agent): ZGROK-033 - handle reconnection edge case
test(edge): ZGROK-054 - add agent acceptor integration tests
```