Patterns & Conventions
What I've learned about how we write code in this project.
Go Style
Loop Idiom
Always range N for integer loops. Never for i := 0; i < N; i++. This is a hard rule Fritz set early.
Table-Driven Tests
Every test file uses t.Run with named subtests. The pattern:
tests := []struct {
name string
// inputs...
// expected...
}{
{"descriptive name", ...},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// test body
})
}
Mock Streams for Handlers
Handler tests construct a bytes.Buffer with a raw request, pass it as a stream, and read the response. No QUIC, no network, fast tests.
t.TempDir() for Fixtures
Never create test fixtures in the working directory. Always t.TempDir() — Go cleans it up automatically.
Build & Test
make all # Build everything
make test # Run all tests
make fmt # Format
make vet # Vet
make lint # Run golangci-lint
# Single module / single test
cd server && go test -run TestHandleFetch/path_traversal_blocked ./internal/handler/
# Dev server
./server/bin/demarkus-server -root ./examples/demo-site
Build rule: Always use make client or go build -o bin/<name> ./cmd/<name>/. Never bare go build ./cmd/<name>/ — binaries must land in bin/.
After completing a task: Run bash pre-commit.sh to format, vet, and lint all modules before committing.
Development Workflow
Pre-Commit
Run bash pre-commit.sh before committing. Formats, vets, and lints all modules.
Conventional Commits
Module-scoped: feat(server): description, fix(client): description. This drives auto-versioning and release tagging (server/v0.1.0, etc.).
Git Commits
Fritz handles all commits himself. Never commit on his behalf — just prepare the changes and let him know what's ready.
CI/CD
Tags: server/v0.1.0, client/v0.1.0, protocol/v0.1.0. Push to main triggers auto-release. CI runs test, vet, and golangci-lint for each module.
Architecture (Quick Reference)
Go monorepo, four modules with local replace directives:
protocol/— wire format types, parsing, serialization (no network code)server/— QUIC server (depends on protocol)client/— CLI, TUI, MCP server (depends on protocol)tools/— dev utilities
Protocol constants: port 6309, ALPN "mark", scheme mark://.
See Architecture for full details.
Core Invariants
- Version immutability: every write creates a new version, published versions are permanent
- Security: no tracking, no telemetry, encrypted transport, capability-based auth
Philosophy
Small and Incremental
Every change should be the smallest working increment. Get something tested and working before moving on. Don't batch up large changes.
Robustness First
Handle the error. Test the edge case. Make it correct before making it elegant.
Simplest Solution
Short functions, clear names, obvious flow. If I find myself writing a comment to explain what code does, the code should be rewritten to not need the comment. Comments explain why, not what.
No Over-Engineering
Don't add features beyond what's asked. Don't refactor surroundings while fixing a bug. Don't add abstractions for one-time operations. Three similar lines are better than a premature helper function.
What I've Learned About Working With Fritz
Fritz values directness. Short answers over long explanations. Working code over architecture astronautics. He'll push back on unnecessary complexity and he's usually right when he does. The best sessions are when we move fast through small, clean changes — each one tested, each one committed. Momentum matters.