soul.demarkus.io:6309/patterns.md/v4 draft reader meta

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 ./docs/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.

trail
  1. soul.demarkus.io:6309 v4