# Code Standards ## Principles Applied in order: **YAGNI → KISS → DRY**. - No feature flags, no plugin systems, no abstractions for hypothetical callers. - One way to do a thing. A helper emerges only after the second call site, never before. - Three similar lines beats a premature abstraction. Extract when a fourth arrives. ## Go conventions ### File naming - Multi-word files use `snake_case` (Go ecosystem standard): `repos_per_language.go`, `contributions_all_time.go`. - Single-word files stay single-word: `client.go`, `model.go`, `profile.go`. - Test files adjacent to the unit under test with `_test.go` suffix. ### Package structure - `main.go` at repo root — the CLI wrapper only. - All reusable code under `internal/` so it can't accidentally become an API. - Each package owns one concern: `github` = network, `card` = render, `theme` = palette data. ### Error handling - Wrap errors with `fmt.Errorf("%w: …", err)` when adding context; bare return when the caller already has enough context. - Sentinel errors (`errors.New`) only when callers need to type-check. We have none today — don't invent them. - Network/API errors in fetchers bubble up; `main.go` decides whether to exit or warn. ### No hidden state - Profile fetchers mutate the `*Profile` argument in-place (`FetchContributionsAllTime(ctx, p, opts)`) — explicit ownership, no package-level caches. - Renderers are pure functions of `(*Profile, theme.Theme)`. No side effects, no goroutines. ### Comments Default: **no comments**. Exceptions: - **Why non-obvious code is non-obvious.** Example: the `scaleFactor = 10_000` constant — a comment explains why fixed-point and not float. - **Hidden invariant or constraint.** Example: the comment on `contributionYearQuery` noting the `maxRepositories: 100` cap. - **Workaround for an upstream behavior.** Example: clamping the current year's `to` to `now` so GitHub doesn't reject future timestamps. Never write comments that describe what well-named code does (`// increment counter`). Never reference "the recent fix" or "for issue #42" — that belongs in git. ### Function length - No hard limit. 200-line functions are fine when the logic is linear. - Extract a helper only when (a) the same shape repeats twice, or (b) a block needs an independent name to be read at the call site. ### Exported vs unexported - Start unexported. Export only when a test file or another package needs the symbol. - Types on the public API: `Profile`, `RepoInfo`, `LangStat`, `LangEdge`, `DailyContribution`, `FetchOptions`, `Client`, `Theme`, `Card`. - Everything else stays lowercase. ## SVG output standards - Always XML-escape user-controlled strings through `escapeXML` (`&`, `<`, `>`, `"`, `'`). - Long user-controlled strings (bio, names, company, repo slugs) go through `truncate` in `svg.go` before rendering — never print raw multi-line input that could push a later element off-screen. - Body numbers formatted via `formatInt` (thousands separators). Y-axis tick numbers go through `formatTick`, which abbreviates ≥ 1000 to `k` / `M` / `B` so no label exceeds 4 chars. - Stable viewbox per card (`340×200`) matching github-profile-summary-cards. - Titles render through `header()` which auto-shrinks font-size (15 → 11 px) when the string wouldn't fit in `width − 20 − 4`. - No `