Skip to content

3. Conventions

The rules every change follows.

Code

  • Use the make targets (make help): CI runs the same ones.
  • Docstrings on public functions and classes (Google style): they generate the API reference.
  • Settings through ApplicationSettings (settings.py, pydantic-settings), not os.environ scattered in the code.
  • Logging with the package's loguru logger, not print:

    from personal_os_setup.settings import logger
    
    logger.info("Loaded {} items", count)
    
  • Log, then raise; do not swallow exceptions:

    if not path.exists():
        logger.error(f"File not found: {path}. Current directory: {Path.cwd()}")
        raise FileNotFoundError(path)
    

    In an except block, logger.exception(...) logs the message and the stack trace. Not raising is fine only when a loop must go on after one failure.

  • Large data: Polars. CPU-bound work: multiprocessing pool map.

  • System-changing actions (installs, chezmoi apply, drivers, sudo) always go through the TUI's confirmation dialog.

Branches and pull requests

  1. Comment on (or create) the issue and wait to be assigned.
  2. Branch off dev: feature/<name> or bugfix/<name>. Stay up to date with git merge dev (branch already pushed) or git rebase dev (local only).
  3. Run make check (4. Testing), then open a pull request to dev. Its title follows the commit convention below: it becomes the squash commit and decides the version.
  4. When dev is ready, a pull request dev โ†’ main releases it.
  5. After a release, merge main back into dev (merge commit, direct push). On conflicts on CHANGELOG.md or a pyproject.toml version, keep main's and regenerate uv.lock with uv lock.
Merge Strategy Why
feature/* โ†’ dev squash one conventional commit per feature
dev โ†’ main squash one commit for semantic-release to evaluate
main โ†’ dev (sync) merge commit, direct push syncs stable metadata; [skip ci] in the release commit prevents a spurious RC

Never edit a version in pyproject.toml or create a tag by hand: semantic-release does both.

Commit convention

[emoji] <type>[(<scope>)][!]: <description>, the emoji is optional (Gitmoji).

Emoji Type Aliases Release
๐Ÿ› fix bugfix, hotfix patch
โšก๏ธ perf patch
โœจ feat feature minor
๐Ÿ’ฅ feat! BREAKING CHANGE: footer major
โ™ป๏ธ ๐Ÿ“ ๐ŸŽจ ๐Ÿงช ๐Ÿ”ง ๐Ÿ‘ท ๐Ÿ“ฆ refactor, docs, style, test, chore, ci, build none

A ! or a BREAKING CHANGE: footer makes any allowed type a breaking change (major release).

โœจ feat(auth): add GitHub App token rotation
๐Ÿ› fix(llm): handle empty response from the API
๐Ÿ’ฅ feat!(api): remove the v1 endpoint

feat(auth): redesign token flow

BREAKING CHANGE: /v1/token removed, use /v2/auth instead

On dev, a releasable commit increments the release-candidate counter (1.1.0-rc.1 โ†’ 1.1.0-rc.2) unless a larger bump is needed (for example, a breaking change โ†’ 2.0.0-rc.1 when the last stable release was 1.0.0).

Scopes are optional. Common scopes: tui, wsl, chezmoi, frontend, ci, deps. The commit message is checked on commit (commitizen) and the pull request title by ci.yml.

Secrets

  • Never write a credential to a file of this public repository, even untracked; use environment variables or gh auth login.
  • detect-secrets runs on commit. For a line that intentionally references a secret placeholder (${{ secrets.X }}), add # pragma: allowlist secret. Keep suppressions inline; never exclude a whole file to silence one line.

Environment variables

Adding, renaming or removing one, in the same change: the setting in settings.py, and the workflow env: if CI needs it. Update the release credentials if a release target reads it.

Dependencies (Renovate)

Renovate (.github/renovate.json) opens pull requests to dev with the renovate and dependencies labels; most routine updates have a 7-day cooldown; security fixes are not grouped and open at once. Runtime dependencies include exact pins and ranges; uv.lock makes builds reproducible. Review every Renovate pull request; a dependency dashboard issue lists the pending ones.

  1. Renovate does not widen the Python range. The supported interpreter moves by hand, together with the CI action and this document.
  2. PyPI updates use strict Python compatibility filtering. Weekly lockfile maintenance refreshes versions already permitted by dependency ranges; review the lockfile and CI results too.
  3. Renovate updates dependency files, GitHub Actions and pre-commit revisions; update README and developer documentation when the supported Python range changes. Docs are not rewritten automatically.
  4. Renovate tracks the Node pin used by pre-commit. Stable updates wait seven days and use chore; major upgrades require dependency dashboard approval.