Files
egress-proxies-operator/CLAUDE.md
Jan Novak 95c487415b Add Gitea Actions image-build workflow
Distilled from the house pattern across sibling projects: tag push +
workflow_dispatch triggers, REGISTRY_TOKEN login, raw docker build/push
to gitea.home.hrajfrisbee.cz. Adds a lightweight test gate, an immutable
sha-<12> tag, :latest only on real tag pushes, and a concurrency group.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 19:39:23 +02:00

10 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 slog for structured logging.
  • context.Context is 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.Join for 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.gofoo_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 integration and use testcontainers-go rather 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.

Status checklist. The top of the file, above the first step's entry, is a markdown checklist with one line per step from the plan (- [ ] Step 0 — <title>), so a single glance shows what's done and what's left without reading prose or cross-referencing git log. Check a box in the same commit that adds that step's entry — never leave a completed step unchecked or a not-yet-done step checked. This checklist is the authoritative status record; an in-session todo list (e.g. TodoWrite) is ephemeral and does not substitute for it — a new session resuming this plan should be able to read this file alone and know exactly where to pick up.

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:

  1. Create a branch off main before 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
  2. Commit on the branch following the commit conventions below.

  3. Push the branch to origin with -u so it tracks.

  4. Open the MR with tea rather than printing a compare URL:

    tea pr create \
      --title "<short title>" \
      --description "<body>" \
      --base main \
      --head <branch>
    

    tea is already authenticated against the Gitea instance; just run it. Print the resulting PR URL for the user. If tea is 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.

  5. Do not merge or delete the branch from the CLI — neither via tea, gh, nor git 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>

CI/CD

.gitea/workflows/build.yaml builds the manager image and pushes it to the Gitea registry. Triggers: any tag push, or manual workflow_dispatch with a tag input. A lightweight check job (go vet / go build / go test -short) gates the build.

  • Images: gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator:<tag> plus an immutable sha-<12-char-commit> tag on every build; :latest moves only on real tag pushes, never on manual dispatch.
  • Requires the REGISTRY_TOKEN repo secret (Gitea PAT with write:package), same convention as the other projects on this Gitea instance.
  • The commit is baked into the binary via the GIT_COMMIT build arg (see Dockerfile).

Gotchas

  • This repo currently contains only go.mod (module gitea.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.