Codifies the pattern the rewritten Step 0 entry demonstrated: one line of context before each command snippet explaining why it was run that way, explicit call-outs when something deviated from the plan rather than presenting the working command as the first thing tried, and a closing "worth noting" paragraph. Points at the Step 0 entry itself as the reference example so future steps match its depth without re-deriving it. Co-Authored-By: Claude <noreply@anthropic.com>
9.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
A Kubernetes operator for managing egress proxies — currently an empty scaffold with no controller code yet. Intended shape: a controller-runtime manager reconciling custom resources (or existing workload types) to provision/configure egress proxy infrastructure. TODO: confirm the actual CRD(s)/API group once implemented.
Commands
No Makefile, linter, or entrypoint exists yet. Verified state as of bootstrap:
go build ./... # nothing to build yet — no .go source files exist
go test ./... # no test files exist yet
No linting or formatting tools are configured in this project yet — golangci-lint
and gofumpt are pre-approved in .claude/settings.json for when they're wired up,
but neither has a config file or CI step yet. There is no main.go or cmd/
entrypoint yet, so there's nothing to run locally.
Architecture
Not yet built. TODO: once the controller-runtime manager and reconcile loop exist, document the data-flow diagram and package/role table here, following the standard reconcile-loop shape: fetch → resolve → list related → compute desired state → merge → patch only on diff.
Conventions
Go Conventions
- Use the latest stable Go version (check https://go.dev/dl/ before starting a new feature).
- Use
slogfor structured logging. context.Contextis the first parameter of every function that does I/O or can be cancelled.- No
init()unless there is no other option. - Error wrapping:
fmt.Errorf("...: %w", err)for single errors;errors.Joinfor multiple. - Prefer stdlib over third-party dependencies where reasonable.
- No vendoring unless explicitly requested.
- Fail fast, return early.
- No obvious comments.
Testing
All new Go code must have corresponding tests.
- Use stdlib
testing— no testify unless already present in the module. - Table-driven tests are the default pattern.
- Test files live next to source:
foo.go→foo_test.go. - Name tests
Test<Function>_<scenario>(e.g.TestParseRoster_emptyInput). - Use
t.Parallel()where safe. - Use subtests for table entries:
t.Run(tc.name, func(t *testing.T) { ... }). - Test behaviour, not implementation.
- Guard slow tests with
testing.Short():if testing.Short() { t.Skip() }. - Integration tests that need real infra (DB, network) go behind
//go:build integrationand usetestcontainers-gorather than mocking the infra away.
Useful test commands
go test ./... # all unit tests
go test -v ./... # verbose
go test -cover ./... # with coverage
go test -short ./... # skip slow/integration tests
go test -v ./internal/... # specific package tree
go test -race -v -run TestName ./path/to/package # single test, race detector on
go test -tags=integration ./... # integration tests (requires infra)
Plans
When Claude Code's plan mode is used, save the plan file inside the repo at
docs/plans/YYYY-MM-DD-HHMM-<slug>.md instead of the default ~/.claude/plans/
location. Get the timestamp with date "+%Y-%m-%d-%H%M". The <slug> is a short
kebab-case summary of the plan's topic.
Include the same timestamp in the plan's header, e.g.:
# Plan: <title>
**Created:** 2026-08-07 15:30
Create docs/plans/ on first use. Plan files are committed to the repo so other
contributors can review historical decisions.
Ordering matters: plan mode itself restricts writes to the default
~/.claude/plans/ location — that's a hard tool restriction during plan mode, not a
choice. So the copy into docs/plans/ cannot happen until after ExitPlanMode.
When it happens, it must be the very first action taken post-exit, committed on its
own, before any implementation work starts (branching, scaffolding, editing code).
Do not fold the plan-file commit into the first implementation commit, and do not
defer it until "later" in the same turn — treat "save and commit the plan" as its own
checkpoint that blocks starting Step 0.
Plan execution summaries
After finishing each step of an approved plan, append a short, human-readable summary
to docs/plans-executions/YYYY-MM-DD-HHMM-<slug>.md — same timestamp and slug as the
originating plan in docs/plans/, so the two files pair up 1:1. Create the file (and
docs/plans-executions/ on first use) after the first step completes; append a new
section per step after that, in chronological order (bottom of the file), not
newest-first like CHANGELOG.md.
Keep the prose light — a few sentences on what was done, plus anything genuinely interesting or non-obvious about how it went (a judgment call made, a spec gap found, something that didn't work as expected). This is not a duplicate of the plan or the diff; skip steps that went exactly as planned with nothing worth flagging.
Include exact commands. Any invocation of an external tool that isn't a plain file
edit — CLI scaffolding tools (kubebuilder, code generators), package installs, other
non-obvious shell commands — goes in as a fenced code block with the exact
command-line actually run, not a paraphrase. Prefer the real invocation over a
description of it; a future reader (or a future Claude) should be able to copy the
snippet and reproduce the step. Trim noisy stdout, but keep anything that changed the
outcome (a flag that mattered, an unexpected error, a version that got picked
automatically).
Structure each command snippet with one line of context before it — why it was run
that way, not just that it was run (e.g. "installed into a scratch GOBIN because the
plan's install path 404s" beats a bare command with no framing). When something
deviated from what the plan said — a flag correction, a version resolved differently
than expected, a tool auto-running another tool — call that out explicitly rather than
presenting the final working command as if it were the first thing tried. Close the
entry with a short "worth noting" paragraph covering anything a future reader should
know before touching this step's output: defaults that came out differently than
planned, files outside the plan's scope that got touched, things deliberately left for
a later step.
Use the Step 0 entry in docs/plans-executions/2026-08-07-1747-proxy-operator.md as
the reference example for the level of detail expected.
Commit the summary update together with that step's implementation commit.
Changelog
Maintain a running changelog in CHANGELOG.md at the repo root. After every
significant change, fix, or update — once the user confirms it works — append a new
entry at the top of the file in this format:
## YYYY-MM-DD HH:MM TZ — short title
- One-line summary of what changed and why.
- Key files touched (optional, only if useful for traceability).
Get the timestamp with date "+%Y-%m-%d %H:%M %Z". Never write a literal placeholder
like HH:MM — always run date first to get the real current time. Skip trivial edits
(typos, formatting, comment tweaks); only log changes a future reader would care about.
Branching & Merge Requests
The remote is Gitea (gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator). For
features, do not commit to main directly. Use a branch + merge request flow:
-
Create a branch off
mainbefore starting work:feat/<slug>for features (e.g.feat/roster-import)fix/<slug>for bug-fix branches the user explicitly asks for<slug>is short kebab-case
-
Commit on the branch following the commit conventions below.
-
Push the branch to
originwith-uso it tracks. -
Open the MR with
tearather than printing a compare URL:tea pr create \ --title "<short title>" \ --description "<body>" \ --base main \ --head <branch>teais already authenticated against the Gitea instance; just run it. Print the resulting PR URL for the user. Ifteais unavailable, fall back to printing the compare URL (gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/compare/main...<branch>) and let the user open the MR manually. -
Do not merge or delete the branch from the CLI — neither via
tea,gh, norgit push --delete. The user does that in Gitea.
Exceptions — committing straight to main is fine for:
- Small bug fixes / hotfixes the user describes as such.
- Typo / comment / formatting tweaks.
- Edits the user explicitly says to push to
main.
When uncertain whether something is a feature or a small fix, ask before branching.
Git Commits
Always append a Co-Authored-By trailer to indicate AI assistance:
Co-Authored-By: Claude <noreply@anthropic.com>
TODO: no .gitea/workflows/ CI pipeline exists yet — add a CI/CD subsection here once
one is set up.
Gotchas
- This repo currently contains only
go.mod(modulegitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator) — no controller code,main.go, Dockerfile, k8s manifests, Makefile, or CI config exist yet. Nothing in this file describes working software; it's the convention scaffold to build against.