You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add coding conventions derived from recent PR review feedback
Captures patterns flagged in reviews across interface design,
testing, operator development, and PR hygiene — so they apply
automatically going forward rather than being caught in review.
Copy file name to clipboardExpand all lines: .claude/rules/go-style.md
+55Lines changed: 55 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,6 +13,36 @@ Applies to all Go files in the project.
13
13
- Separate business logic from transport/protocol concerns
14
14
- Keep packages focused on single responsibilities
15
15
16
+
## Interface Design
17
+
18
+
Check these whenever adding a method to an interface or defining a new type:
19
+
20
+
-**Minimal surface**: Don't add interface methods that duplicate the semantics of existing ones. If an existing method already answers the question (possibly with a side effect), don't add a separate method for the same check.
21
+
-**No no-op implementations**: If a method is only meaningful for some implementations of an interface, the interface is too broad. A no-op silently breaks callers that depend on the method working. Narrow the interface or use a separate capability interface.
22
+
-**Avoid parallel types that drift**: Don't define a separate config/data type that mirrors an existing one. Embed or reuse the original — two parallel structs require a conversion step and will diverge over time.
23
+
24
+
## Constants for Repeated String Literals
25
+
26
+
When a string literal is used as a key or identifier in three or more places, extract it to a named `const`. Silent typos in repeated raw strings are harder to find than a single compile-time reference.
27
+
28
+
```go
29
+
// Good
30
+
const metadataKeyBackendURL = "backend_url"
31
+
32
+
// Avoid: "backend_url" repeated inline across 3+ call sites
33
+
```
34
+
35
+
## URL Validation
36
+
37
+
Whenever code calls `url.Parse` and then uses `Scheme` or `Host`, validate the result. `url.Parse` accepts malformed or relative inputs without error — a bare hostname parses successfully with empty `Scheme` and `Host`. When an absolute URL is required:
- Use `fmt.Errorf` with `%w` to preserve error chains; don't wrap excessively
55
85
- Use `recover()` sparingly — only at top-level API/CLI boundaries
56
86
87
+
## In-Memory Caches Must Be Bounded
88
+
89
+
Any in-memory cache must have a capacity limit. Without one, entries that are never explicitly evicted accumulate indefinitely — leaking memory, connections, or file handles. Apply this whenever you introduce a `sync.Map`, a plain `map` guarded by a mutex, or any other structure used as a cache:
90
+
91
+
- Set a maximum entry count and evict on overflow (LRU, FIFO, or random).
92
+
- Document the bound and the eviction policy in the type's doc comment.
93
+
- If Redis or another remote store provides TTL-based expiry, the local cache still needs a size cap — remote TTLs do not bound local memory.
94
+
95
+
## Document Architectural Constraints on Exported Functions
96
+
97
+
When an exported function or constructor changes behavior based on injected infrastructure (storage backend, transport mode, external client), its doc comment must state what the injection does and does not solve. Callers cannot be expected to infer distributed-system constraints from the implementation.
98
+
99
+
Include at minimum:
100
+
- What the injected component enables (e.g., cross-replica metadata sharing).
101
+
- What it does *not* solve (e.g., cross-replica message delivery, fan-out).
102
+
- Any caller responsibility that follows (e.g., session affinity at the load balancer).
103
+
104
+
## Concurrency Comments
105
+
106
+
Keep comments about mutexes, locks, and concurrency accurate — they are easy to get wrong and mislead future readers:
107
+
108
+
- Only say a lock "must be held" or "is already held" if you have verified it at that call site.
109
+
- Do not claim an operation would deadlock without confirming that the lock in question would actually be re-acquired.
110
+
- When a comment describes a concurrency invariant (e.g., "called with mu held"), add it to the function's doc comment so it travels with the signature, not inline at the call site.
111
+
57
112
## Logging
58
113
59
114
-**Silent success** — no output at INFO or above for successful operations
Copy file name to clipboardExpand all lines: .claude/rules/operator.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -35,6 +35,10 @@ See `cmd/thv-operator/DESIGN.md` for detailed decision guidelines.
35
35
- Chainsaw tests require a real Kubernetes cluster
36
36
- Status updates require a separate client patch (`r.Status().Update()`)
37
37
38
+
## Status Condition Parity
39
+
40
+
When adding a status condition to one CRD type, check all parallel types (e.g., `MCPServer` and `VirtualMCPServer`) for the same condition. Conditions that warn about misconfiguration or unsupported states should be consistent across types that share the same feature set — a gap means one type silently accepts invalid config that the other rejects.
Copy file name to clipboardExpand all lines: .claude/rules/pr-creation.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,6 +16,10 @@ Do NOT leave optional sections empty or with only placeholder/template text. Eit
16
16
-**Does this introduce a user-facing change?**: Describe the change from the user's perspective. Write "No" if not applicable.
17
17
-**Special notes for reviewers**: Non-obvious design decisions, known limitations, areas wanting extra scrutiny, or planned follow-up work.
18
18
19
+
## PR Scope
20
+
21
+
Each PR must contain only related changes. If a bug fix, refactor, or unrelated cleanup is discovered while working on a feature, open a separate PR for it. Mixed-scope PRs are harder to review and harder to revert cleanly.
22
+
19
23
## Style guidelines
20
24
21
25
- Keep the PR title under 70 characters, imperative mood, no trailing period.
Copy file name to clipboardExpand all lines: .claude/rules/testing.md
+31-1Lines changed: 31 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,9 +32,13 @@ Applies to test files and test directories.
32
32
2.**Redundancy**: Avoid overlapping test cases exercising the same code path
33
33
3.**Value**: Every test must add meaningful coverage — remove tests that don't
34
34
4.**Consolidation**: Consolidate small test functions into a single table-driven test when they test the same function
35
-
5.**Naming**: Use clear, descriptive test names that convey the scenario
35
+
5.**Naming**: Test names must match what they actually assert — if the assertion changes, update the name too.
36
36
6.**Boilerplate**: Minimize setup code; extract shared setup into helpers with `t.Helper()`
37
37
38
+
## E2E Test Coverage
39
+
40
+
E2E tests must verify functional behavior, not just infrastructure state. Confirming that pods are ready or that counts are correct is not sufficient — the test must also exercise the actual code path (send traffic, trigger the feature) to prove it works end-to-end.
41
+
38
42
## Test Scope
39
43
40
44
Tests must only test code in the package under test. Do NOT test behavior of dependencies, external packages, or transitive functionality.
@@ -55,3 +59,29 @@ Write tests isolated from other tests that may set the same env vars. Use `t.Set
55
59
## Port Numbers
56
60
57
61
Use random ports (e.g., `net.Listen("tcp", ":0")`) to let the OS assign a free port. Do not use hardcoded port numbers — even large ones can clash with running services.
62
+
63
+
## Test Hooks in Production Structs
64
+
65
+
Do not add test-only hook fields (nil-checked `func()` fields) to production structs. A field documented as "nil in production" signals the concern belongs outside the production type. Preferred alternatives:
66
+
67
+
-**Interface seam**: Replace the internal component with an interface; tests inject a wrapper that adds the needed synchronization or observation.
68
+
-**Functional constructor options**: Expose hook injection only through a constructor option so the production call site stays clean.
69
+
-**Test at the observable boundary**: Control timing through the mock/stub's own behavior rather than hooking into production internals.
70
+
71
+
## Concurrent Tests: Always Add Timeouts to Blocking Barriers
72
+
73
+
Blocking operations in tests (`WaitGroup.Wait()`, channel receives, `sync.Cond.Wait()`) must have a timeout/fail-fast path. Without one, a panicking goroutine or regression in synchronization logic causes the test to hang until the global `go test` timeout.
74
+
75
+
```go
76
+
// Good: fail fast with a clear message
77
+
done:=make(chanstruct{})
78
+
gofunc() { wg.Wait(); close(done) }()
79
+
select {
80
+
case<-done:
81
+
case<-time.After(5 * time.Second):
82
+
t.Fatal("timeout waiting for goroutines to synchronise")
Copy file name to clipboardExpand all lines: .claude/rules/vmcp-anti-patterns.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -79,3 +79,11 @@ Adding caches, connection pools, or other performance optimizations without evid
79
79
**Detect**: Caches introduced without profiling data or load estimates showing the uncached path is too slow; connection pools or object pools where the allocation cost hasn't been measured; complexity added to avoid overhead (e.g., TLS handshakes, serialization) at request rates where the overhead is negligible.
80
80
81
81
**Instead**: Start with the straightforward implementation. Measure under realistic load. Add optimization only when measurements show it's needed, and document the evidence in the commit or PR description.
82
+
83
+
## 10. Mutable Domain Objects with Mutex Protection
84
+
85
+
Adding a mutex to a domain object (session, config, metadata record) and mutating it in place when state changes. This pattern requires callers to coordinate locking, produces stale copies on other replicas, and adds complexity every time a new mutation is needed.
86
+
87
+
**Detect**: Mutex fields on domain structs; mutation methods on types that were previously read-only; in-place map or field writes guarded by an object-level lock.
88
+
89
+
**Instead**: Treat domain objects as immutable snapshots. When state changes, update the storage layer and evict the local cache entry. The next read reconstructs the object from updated storage — no mutation, no lock, and cross-replica consistency is automatic because all replicas read from the same source of truth.
0 commit comments