mirror of
https://github.com/tiennm99/ghstats.git
synced 2026-05-14 10:58:25 +00:00
9e435f631f
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.
104 lines
5.0 KiB
Markdown
104 lines
5.0 KiB
Markdown
# 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.
|