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

221 lines
10 KiB
Markdown

# 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:
```bash
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.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 integration`
and use `testcontainers-go` rather than mocking the infra away.
#### Useful test commands
```bash
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:
```bash
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.