Files
ghstats/docs/code-standards.md
tiennm99 9e435f631f docs: focus each file on users or coworkers, drop unrelated content (#18)
project-roadmap.md went from a 146-line phase-by-phase history to a
48-line focused view: what's planned, what's out of scope. Completed
work is already in git log + GitHub Releases — the doc re-telling it
was the thing most likely to rot and least likely to be read.

project-overview-pdr.md: "Open questions" section dropped its stale
bullet list and now just points at project-roadmap.md (single source of
truth for planned work).

code-standards.md: drop the ".claude/ directory" commit rule — that's
a per-user workflow detail, not a project-level standard. Docs are for
users of the CLI/Action and coworkers of this repo, nothing else.
2026-04-19 10:34:02 +07:00

104 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `<script>` tags, no event handlers. Cards are pure markup.
## Testing
- Unit tests in the same package, no mocking of http.Client — we test rendering and pure helpers.
- Network tests are omitted; integration verification is manual (`./ghstats -token ...`).
- `go vet ./...` clean before every commit.
## Commit conventions
- Conventional commit prefixes: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `build:`, `ci:`, `chore:`.
- Scope is optional: `refactor(card): …`.
- No AI references. No "generated by" footers. Keep the subject ≤ 72 chars.
## Pre-commit checklist
```
go vet ./...
go test ./...
go build ./...
```
All three must pass. If a test is failing, fix the test before committing — don't skip.
## Card review checklist (pre-release)
In addition to the compile/test gate above, any change that touches files under `internal/card/` **must** be reviewed against `docs/design-guidelines.md` → "Fit-the-frame invariant" before merging. Specifically:
- Render the dracula theme against the **tiny / typical / adversarial** profile fixtures and visually verify nothing overflows or overlaps.
- No hard-coded dimensions that only work for the reviewer's own profile (names, digit counts, year spans).
- Every right-anchored value or text-anchor="middle" element fits inside its column / safety margin.
- The CI-built `demo/<theme>/` gallery is the canonical "dracula on the author's profile" view; don't rely on a local run alone.
This is a hard gate: a card that overflows on a realistic profile does not ship, even if tests pass.
## Dependency policy
- **Stdlib only.** `go.mod` lists no `require` entries.
- If a future feature needs a third-party module, add it in a dedicated commit with justification in the message.
- Keep binary size under 15 MB.