This document defines how we work. Its purpose is to make the codebase predictable — so that six months from now, reading any commit, branch name, or PR description tells you exactly what happened and why.
- Branching strategy
- Commit messages
- Pull requests
- Code review
- Branch protection rules
- Code quality standards
- Dependency management
We use a lightweight trunk-based model with a staging buffer.
main ─────────────────────────────────────────────── production-ready, tagged releases
└─ develop ────────────────────────────────────── integration branch, always green CI
├─ feature/42-instagram-carousel
├─ fix/38-heic-exif-timezone
├─ chore/update-anthropic-sdk
└─ docs/adr-photo-embed-strategy
| Prefix | Purpose | Merges into | Example |
|---|---|---|---|
feature/ |
New capability | develop |
feature/14-elevation-svg |
fix/ |
Bug fix | develop |
fix/22-missing-exif-fallback |
chore/ |
Deps, CI, refactoring, tooling | develop |
chore/upgrade-pillow-11 |
docs/ |
Documentation only, no code change | develop |
docs/adr-bilingual-output |
hotfix/ |
Critical production fix | main AND develop |
hotfix/api-key-leak |
- Always branch from
develop, notmain(excepthotfix/). - Branch names are lowercase and hyphen-separated. No underscores, no camelCase.
- Include the issue number when one exists:
feature/42-instagram-carousel. - Delete the branch after it is merged. Keep the remote clean.
- Never commit directly to
mainordevelop. Always via PR. mainonly receives merges fromdevelop(scheduled releases) orhotfix/branches.
# Good
git checkout -b feature/14-gpx-elevation-profile
git checkout -b fix/27-photo-sort-timezone
git checkout -b chore/add-mypy-config
git checkout -b docs/update-quickstart
# Bad
git checkout -b my-feature
git checkout -b Feature_14
git checkout -b fixBugWe follow Conventional Commits. Every commit message must be parseable by a machine and readable by a human.
<type>(<scope>): <short description>
[optional body — wrap at 72 chars]
[optional footer: BREAKING CHANGE or Closes #N]
| Type | When to use |
|---|---|
feat |
New feature visible to the user |
fix |
Bug fix |
chore |
Build, deps, CI — no user-facing change |
docs |
Documentation only |
test |
Adding or fixing tests only |
refactor |
Code change that is neither a fix nor a feature |
perf |
Performance improvement |
style |
Formatting only (whitespace, commas) — no logic change |
gpx, photos, llm, html, instagram, cli, config, ci, deps
feat(llm): add photo selection prompt with arc-based guidance
fix(photos): fall back to file mtime when EXIF timestamp is missing
chore(deps): upgrade anthropic SDK to 0.40.0
test(gpx): add fixture for GPX file with no elevation data
docs(adr): record decision to embed photos as base64
feat(html): add RU/EN language toggle to memory page template
BREAKING CHANGE: NarrativeOutput now requires title_ru field
- Subject line: 50 chars max, imperative mood ("add X", not "added X" or "adding X").
- No period at the end of the subject line.
- Body explains WHY, not what (the diff already shows what).
- One logical change per commit. If you have to use "and" in the subject, split it.
- Your branch has a passing CI run.
- You've run
make cilocally without errors. - New code has tests. New features have documentation.
- The PR does one thing. If it grew into two things, split it.
Follow the same Conventional Commits format as commit messages.
feat(llm): implement photo selection with narrative arc guidance
fix(photos): handle HEIC files without EXIF metadata
chore: upgrade to Python 3.13
The repository contains a PR template that loads automatically. Fill it in — do not delete sections.
| Lines changed | Verdict |
|---|---|
| < 200 | Ideal — review in under 15 min |
| 200–500 | Acceptable — add extra context in description |
| > 500 | Split it unless it is a single atomic change (e.g. generated file) |
Open as Draft if you want early feedback or CI runs before the code is ready for review. Never request review on a Draft PR.
- PRs into
develop: Squash and merge — keeps history linear. developintomain: Merge commit — preserves the integration history.hotfix/intomain: Merge commit, then immediately mergemainback intodevelop.
A review approval means: "I have read this, I understand it, and I am comfortable with it shipping." Not: "It looks fine, I skimmed it."
- Does it do what the PR description says?
- Are there tests? Do they test the right thing?
- Are new Pydantic models correctly typed?
- Are new LLM prompts in
llm/prompts.py(not scattered in code)? - Does it handle the failure cases (missing EXIF, API timeout, malformed GPX)?
- Does it log API keys, user data, or file paths inappropriately?
Use these prefixes to signal intent:
| Prefix | Meaning |
|---|---|
nit: |
Minor style preference — author decides, no action required |
suggestion: |
Better approach exists — worth considering, not blocking |
question: |
Needs clarification before approval |
blocking: |
Must be resolved before merge |
nit: this variable name could be more descriptive
suggestion: consider using a generator here to avoid loading all photos into memory
question: does this handle GPX files with no trackpoints?
blocking: this logs the API key on line 42
Configure these in GitHub → Settings → Branches.
| Rule | Setting |
|---|---|
| Require PR before merging | ✅ |
| Required approvals | 1 |
| Dismiss stale reviews on new commits | ✅ |
| Require status checks to pass | ✅ ci / quality |
| Require branches to be up to date | ✅ |
| Do not allow bypassing above settings | ✅ |
| Allow force pushes | ❌ |
| Allow deletions | ❌ |
| Rule | Setting |
|---|---|
| Require PR before merging | ✅ |
| Required approvals | 1 |
| Require status checks to pass | ✅ ci / quality |
| Allow force pushes | ❌ |
| Allow deletions | ❌ |
The scripts/setup_github.sh script configures these automatically via the GitHub CLI.
| Tool | Purpose | Config |
|---|---|---|
ruff |
Lint + format | pyproject.toml |
mypy |
Static type checking | pyproject.toml |
pytest |
Tests + coverage | pyproject.toml |
- Type hints on all public functions and class attributes.
- Docstrings on all public functions and classes. One-line is fine for simple functions.
- Coverage ≥ 80% on
trailstory/(excludingcli.py). CI enforces this. - No
# type: ignorewithout an accompanying comment explaining why. - No bare
except:— always catch a specific exception type.
make lint # ruff check .
make format # ruff check --fix . && ruff format .
make typecheck # mypy trailstory/
make test # pytest --cov=trailstory
make ci # all of the above in sequencemake setup installs pre-commit and registers the hooks defined in
.pre-commit-config.yaml — ruff (with
auto-fix), ruff-format, detect-secrets against .secrets.baseline,
and a 1 MB cap on newly-added files. The setup target is idempotent;
re-running it is the supported way to refresh hook environments after
pulling new versions.
make setup # one-shot: venv + deps + git hooks + pre-commit
pre-commit run --all-files # run every hook against the whole repo
make ci # CI-equivalent (lint, format, typecheck, tests)The hooks run on git commit against staged files only; make ci
remains the authoritative pre-push gate. If detect-secrets flags a
new finding that is in fact safe (e.g. a fixture-only fake token),
update the baseline with
detect-secrets scan --baseline .secrets.baseline and commit the
refreshed baseline alongside the change.
Hook versions are pinned in .pre-commit-config.yaml and bumped
weekly by Dependabot — see .github/dependabot.yml.
- All dependencies are declared in
pyproject.toml. - Pin versions for reproducibility: use
>=X.Y, <X+1ranges, not*. - Never use
pip install <package>without adding it topyproject.toml. - Dev dependencies (ruff, mypy, pytest) go in
[project.optional-dependencies] dev. - When upgrading a major version, open a dedicated
chore/PR.
The setup script creates these labels on the repository:
| Label | Colour | Meaning |
|---|---|---|
type: feature |
#0075ca |
New capability |
type: fix |
#d93f0b |
Bug fix |
type: chore |
#e4e669 |
Maintenance |
type: docs |
#0052cc |
Documentation |
priority: high |
#b60205 |
Blocking or urgent |
priority: low |
#cfd3d7 |
Nice to have |
needs: review |
#fbca04 |
Ready for review |
needs: tests |
#e11d48 |
Missing test coverage |
llm |
#7c3aed |
Relates to prompt or LLM logic |