This project uses Claude Code as an AI-powered pair programmer. The human developer directs strategy, approves all artifacts, and maintains quality control. Claude Code executes implementation following strict TDD discipline and mandatory quality gates.
The developer never surrenders decision-making authority. Every interface, every test, every implementation, and every commit requires explicit developer approval before the workflow advances. Claude Code proposes; the developer disposes.
Research --> ADRs --> Planning --> Implementation (TDD per task) --> Completion
Each phase produces artifacts that are committed to git. Each phase requires developer approval before moving to the next. There is no shortcutting -- skipping research leads to bad decisions, skipping planning leads to disorganized implementation, and skipping TDD leads to fragile code.
Claude Code (documentation-specialist agent) researches libraries, patterns, and techniques relevant to the upcoming work. Research documents are created in ai-docs/research/ and committed to git like code.
Process:
- Developer identifies a topic that needs research (e.g., "we need image processing in Go").
- Claude Code researches the topic using web search, library documentation, and API references.
- A research document is created in
ai-docs/research/with findings, comparisons, trade-offs, and recommendations. - Developer reviews the research document.
- Committed with
docs(research): ...prefix.
What research documents contain:
- Problem statement and constraints
- Library/technique comparisons with pros and cons
- API surface analysis and code examples
- Performance characteristics and benchmarks
- Integration considerations specific to this project
- Recommendation with justification
Examples from this project's git history:
| Document | Purpose |
|---|---|
go-image-libraries.md |
Compared stdlib, imaging, bimg, vipsgen for image processing |
go-face-plate-detection-libraries.md |
Evaluated ML inference options for privacy detection |
vipsgen-api-reference.md |
Detailed API reference for chosen image library |
onnxruntime-go-api-reference.md |
API reference for ML inference runtime |
scrfd-model-output-format.md |
SCRFD face detection model output tensor format |
privacy-blur-implementations.md |
Blur techniques for privacy region redaction |
nms-implementation.md |
Non-maximum suppression algorithm research |
scaling-strategy.md |
Horizontal and vertical scaling approaches |
Research produces understanding. ADRs crystallize that understanding into binding decisions. ADRs live in ai-docs/adr/ and serve a dual purpose: they document decisions for human developers AND they constrain Claude Code's behavior in future sessions.
Why ADRs are critical for AI-assisted development:
Claude Code has no memory between conversations. Without ADRs, every new session starts from zero -- Claude Code would re-evaluate decisions that were already made, potentially choosing different libraries, patterns, or approaches each time. ADRs solve this by being included in the project context (via CLAUDE.md references and the ai-docs/ directory). When Claude Code starts a new session, it reads the ADRs and follows those decisions instead of making its own.
This is how architectural decisions persist across conversations.
Process:
- Research reveals that a decision is needed (e.g., "which image library should we use?").
- Claude Code drafts an ADR documenting the decision, alternatives considered, and rationale.
- Developer reviews and approves the ADR.
- Committed with
docs(adr): ...prefix.
ADR format:
- Status: Accepted / Superseded / Deprecated
- Context: The problem or decision point
- Decision: What was decided
- Consequences: Trade-offs and implications
- Alternatives Considered: What was rejected and why
Examples from this project (13 ADRs):
| ADR | Decision |
|---|---|
| 000 | Go as implementation language |
| 001 | Pipes and Filters architecture pattern |
| 002 | Server-side image validation strategy |
| 003 | vipsgen (libvips) for image processing |
| 004 | ONNX format for ML models |
| 005 | onnxruntime_go for inference |
| 006 | SCRFD + YOLOv11 detection models |
| 007 | Four-stage pipeline architecture |
| 008 | Test-Driven Development |
| 009 | Classical/Detroit testing over Mockist/London |
| 010 | Dual-mode integration tests |
| 011 | Goa Design HTTP framework |
| 012 | Valkey over Redis (open-source, BSD-3-Clause) |
Plus cross-project ADRs from follow-api (015, 016, 017, 022) that affect both services.
Before any implementation begins, a plan is created. Plans contain requirements, acceptance criteria, task breakdown, and dependencies. They live in the ai-docs/planning/ directory and follow a strict lifecycle.
Process:
- Developer identifies the next body of work.
- Claude Code creates a plan document in
ai-docs/planning/backlog/. - Developer reviews the plan: Are the tasks correct? Is the ordering right? Are acceptance criteria clear?
- When work begins, the plan moves to
ai-docs/planning/active/. - Committed with
docs(planning): ...prefix.
Plan contents:
- Goal and scope
- Requirements and acceptance criteria
- Task breakdown (ordered)
- Dependencies between tasks
- Integration considerations
- Testing strategy
Plan lifecycle:
backlog/ --> active/ --> completed/ --> archived/ --> git rm
| State | Meaning |
|---|---|
backlog/ |
Approved but not yet started |
active/ |
Currently being implemented |
completed/ |
All tasks done, developer confirmed |
archived/ |
Retained temporarily for reference |
git rm |
Permanently removed from the repository |
Critical rule: old plans must be removed from git.
Completed and archived plans must eventually be git rm'd. This is not mere housekeeping -- it prevents a real and serious problem. Claude Code reads the entire ai-docs/ directory for context. If old plans remain, Claude Code may read them and follow outdated instructions, implement features that were descoped, or apply patterns that were superseded. Removing old plans keeps Claude Code focused exclusively on current, approved work.
This is the core of the workflow. Each task from the active plan follows a strict TDD cycle with developer approval gates at every step. The cycle is: Interface, Stub, RED tests, GREEN implementation, Refactor.
Claude Code (go-backend-engineer agent) creates the interface that defines the contract for the component being built.
- The interface captures the behavioral contract without committing to implementation details.
- Quality gates must pass with zero issues.
- Developer reviews: Is this the right abstraction? Are the method signatures correct? Is the interface minimal (no speculative methods)?
- Committed:
feat(scope): add [InterfaceName] interface
Claude Code creates a stub that satisfies the interface but does no real work.
- Returns
ErrNotImplementedor zero values. - Includes a compile-time interface check:
var _ Interface = (*Impl)(nil) - Quality gates must pass with zero issues.
- This may be part of the interface commit or a separate commit.
Claude Code writes comprehensive tests that exercise the interface contract. These tests MUST genuinely fail -- this is the RED phase, and it is non-negotiable.
Test requirements:
- Table-driven tests with
t.Parallel(). - Classical/Detroit style as mandated by ADR-009:
- Hand-written fakes, NOT mock frameworks (testify/mock is only for external services like MinIO/Valkey).
- Verify outcomes (state, return values), NOT interactions (method call counts, argument matchers).
- Fakes implement the real interface with compile-time checks.
- All quality gates must pass (formatting, linting,
go mod tidy).
What is NOT acceptable in RED tests:
t.Skip()-- masks untested code as "passing"- Commented-out assertions -- tests that do not assert are not tests
// TODOplaceholders for future assertions- Tests that pass against the stub -- if a test passes before implementation, it tests nothing
The RED phase proves that the tests are meaningful. A test that never fails provides no confidence when it passes.
Developer reviews:
- Are these the right tests for this component?
- Do they cover edge cases, error paths, and boundary conditions?
- Are they Classical style (state verification)?
- Do they genuinely fail against the stub?
Committed: test(scope): add [Component] TDD tests (RED phase)
Claude Code implements the minimum functionality required to make all RED tests pass.
- The goal is correctness, not elegance. Get the tests green first.
- ALL tests in the entire project must pass:
go test -race -cover ./... - Quality gates must pass with zero issues.
- Developer reviews the implementation for correctness and approach.
- Committed:
feat(scope): implement [Component] (GREEN phase)
With passing tests as a safety net, Claude Code refactors the implementation for clarity, performance, or better structure.
- All tests must continue to pass after refactoring.
- Quality gates must pass with zero issues.
- Developer reviews the refactored code.
- Committed:
refactor(scope): [description]
Steps 1-5 repeat for each task in the active plan. The developer controls the pace and may reorder tasks, add tasks, or adjust scope based on what is learned during implementation.
These gates are mandatory before the developer looks at any code. This is non-negotiable. Code that does not pass all gates is not ready for human review.
# 1. Format: gofumpt for strict Go formatting, golines for 80-char line limit
gofumpt -w . && golines -w --max-len=80 .
# 2. Lint: go vet + extensive custom linters (including nilaway for nil-safety)
go vet ./... && ./custom-gcl run -c .golangci-custom.yml ./... --fix
# 3. Test: race detector enabled, coverage reported
go test -race -cover ./...
# 4. Dependencies: ensure go.mod and go.sum are clean
go mod tidyWhy the custom linter configuration matters:
The file .golangci-custom.yml configures an extensive set of linters that go far beyond the Go defaults. This includes nilaway (which performs whole-program nil-safety analysis), along with rules for error handling, naming conventions, complexity limits, and more. These linters are a primary mechanism for producing high-quality code. They catch entire categories of bugs that tests alone would miss.
Skipping the linters -- or ignoring their output -- defeats the purpose of the entire workflow.
Rule: Zero tolerance. Every warning, every error must be resolved. There is no "we'll fix that later."
Every step in the workflow produces a commit. Commits are atomic, focused, and descriptive.
Format:
type(scope): description
Types:
| Type | When |
|---|---|
feat |
New functionality (interfaces, implementations) |
fix |
Bug fixes |
refactor |
Code restructuring without behavior change |
docs |
Documentation (research, ADRs, plans, architecture) |
test |
Test additions or modifications |
style |
Formatting, whitespace, import ordering |
improve |
Enhancements to existing functionality |
Documentation commit prefixes:
docs(research): ...-- research documentsdocs(adr): ...-- architecture decision recordsdocs(planning): ...-- plans (creation, moves, completion, removal)docs(architecture): ...-- architecture specifications
Rules:
- One commit per logical change. Do not bundle unrelated changes.
- The agent commits only after the developer has approved the artifact.
- Plans and research are committed to git with the same discipline as code.
When all tasks in an active plan are done:
- Developer confirms that all acceptance criteria are met.
- Plan file moves from
ai-docs/planning/active/toai-docs/planning/completed/. - Committed:
docs(planning): mark [plan] as completed - After a reasonable period, move from
completed/toarchived/. - Eventually,
git rmthe archived plan to remove it from the repository entirely.
The git rm step is not optional. See the explanation in Phase 3 about why old plans must be removed.
Developer stays in control. Every artifact -- interface, test, implementation, ADR, plan -- requires explicit approval. Claude Code proposes, the developer decides. There is no autonomous execution.
Quality is enforced mechanically. The custom linter suite and race-enabled tests catch issues before human review. The developer's time is spent on design decisions and architectural review, not catching nil dereferences or formatting inconsistencies.
TDD prevents regressions. The RED-GREEN-REFACTOR cycle guarantees that tests are meaningful (they failed before implementation) and that the code works (it passes after implementation). Refactoring is safe because the tests provide a safety net.
ADRs enforce decisions across sessions. Claude Code has no persistent memory. ADRs in ai-docs/adr/ are read at the start of every session, ensuring that past decisions are respected. Without ADRs, each session would risk re-litigating settled questions.
Plan hygiene prevents context pollution. Removing completed plans from git ensures that Claude Code only reads current, approved instructions. Old plans in the repository are a source of confusion and contradictory guidance.
Classical testing ensures maintainability. Fakes over mocks means tests verify what the code does, not how it does it. When implementation details change during refactoring, tests that verify state continue to pass. Tests that verify interactions break on every refactor, creating false failures and eroding confidence.
Everything is in git. Research, ADRs, plans, and code all live in the same repository with the same commit discipline. There is a complete, auditable history of every decision and every change.