This repository documents practical engineering conventions for day-to-day development work. It should stay short, current, and easy to apply.
mainshould always be in a deployable state.- Changes should be small, reviewable, and easy to roll back.
- CI is required for shared branches and pull requests.
- Test-driven development is preferred especially when behavior is unclear, risky, or being implemented with AI assistance.
- Incomplete work should be hidden behind feature flags, not long-lived branches.
- Automated tests should exist at the right level for the risk: unit, integration, and end-to-end where appropriate.
Use a trunk-based workflow by default.
| Branch | Purpose | Rules |
|---|---|---|
main |
Source of truth. Always production-ready. | Protected. Requires passing CI and review before merge. |
release/<version> |
Optional short-lived stabilization branch for a scheduled release. | Created only when needed. Deleted after release. |
feat/<name> |
New user-facing functionality. | Short-lived. Rebased or merged frequently from main. |
fix/<name> |
Bug fix that is not an emergency. | Short-lived. Must include tests when practical. |
hotfix/<name> |
Urgent production repair. | Branch from main, fast review, merge back quickly. |
docs/<name> |
Documentation-only changes. | Keep small and easy to review. |
chore/<name> |
Tooling, maintenance, dependency, or config changes. | Prefer isolated, low-risk changes. |
refactor/<name> |
Structural code improvement with no intended behavior change. | Should be supported by tests. |
Do not keep a permanent develop branch unless the team has a concrete release-management reason for it. For most teams, a permanently deployable main branch is simpler and more reliable.
Prefer concise, descriptive branch names:
feat/user-authfix/header-overflowchore/update-eslintdocs/api-paginationrefactor/payment-servicehotfix/login-outage
Avoid generic names such as test, my-branch, updates, or user-specific sandbox branches unless there is a clear temporary need.
- Start from the latest
main.git switch main git pull git switch -c feat/my-change
- Make small commits with clear intent.
- Rebase or merge from
mainregularly if the branch lives longer than a day or two. - Run relevant tests locally before opening a pull request.
- Open a PR early if feedback is useful, but do not merge until required checks pass.
- Merge to
mainonly when the change is reviewable, tested, and safe to deploy.
Prefer short-lived branches. If work is not ready for users but needs to merge, use feature flags, configuration gates, or dark launches.
TDD is not mandatory for every change, but it is the preferred default when:
- The expected behavior can be stated clearly before implementation.
- The change affects business rules, parsing, state transitions, or bug fixes.
- AI is being used to generate or refactor code and you want fast validation of correctness.
Recommended loop:
- Write or update a failing test that captures the intended behavior.
- Implement the smallest change needed to make the test pass.
- Refactor while keeping the test suite green.
Why this matters for AI-assisted coding:
- Tests constrain the solution space and reduce vague prompts.
- Failing tests make regressions visible immediately.
- Passing tests provide a concrete acceptance check for generated code.
- Small test-first steps reduce the risk of large, plausible-looking but incorrect changes.
Do not force TDD where it adds little value, such as trivial presentation changes or exploratory prototyping. But when the cost of wrong behavior is meaningful, a test-first workflow is usually the safest and fastest path.
Every pull request should:
- Be scoped to one logical change.
- Explain the user or system impact.
- Include tests or explain why tests were not added.
- Pass all required CI checks.
- Be small enough for a reviewer to understand without excessive context switching.
Reviewers should focus on correctness, risk, maintainability, and test coverage, not only style.
Branch names and environments should not be conflated.
Use this separation instead:
| Concept | Example |
|---|---|
| Branch | main |
| Release branch | release/2026-03 or release/1.4.0 |
| Tag | v1.4.0 |
| Environment | dev, staging, prod |
Recommended flow:
- Merge reviewed changes into
main. - Deploy
mainautomatically to a non-production environment if useful. - Validate in
staging. - Tag the release commit as
vX.Y.Z. - Deploy that tagged commit to production.
This keeps release history immutable and makes rollback much clearer.
CI should be mandatory for pull requests and protected branches.
Minimum expectations:
- Install dependencies in a reproducible way.
- Run linting, type checks, and automated tests.
- Build the application if it produces deployable artifacts.
- Block merges when required checks fail.
- Deploy from a known commit, ideally the same one that was validated in CI.
Example GitHub Actions workflow:
name: CI
on:
pull_request:
push:
branches:
- main
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint --if-present
- run: npm run typecheck --if-present
- run: npm test --if-present
- run: npm run build --if-presentExample release workflow:
name: Deploy
on:
push:
tags:
- 'v*.*.*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy tagged release
run: ./scripts/deploy-production.shTesting strategy should match risk and feedback speed.
| Test Type | Best For |
|---|---|
| Unit tests | Pure logic, small components, edge cases |
| Integration tests | Database, API, queue, filesystem, and service boundaries |
| End-to-end tests | Critical user journeys and release confidence |
Do not optimize for unit-test count alone. Optimize for confidence per minute of maintenance cost.
When practical, start with the test before the implementation. This is especially useful for AI-assisted code generation, where tests act as an executable specification and a regression boundary.
- Shared branches must stay healthy. If
mainbreaks, fixing it becomes the top priority. - Raise blockers early instead of waiting until a deadline.
- Prefer written decisions in PRs, issues, or design notes so tradeoffs are visible later.
- When behavior changes, document the operational impact, migration needs, and rollback path.
- Write code that is easy to read, change, and delete.
- Prefer explicit names over clever abstractions.
- Keep dependencies current, but upgrade deliberately.
- Use formatting and linting tools to automate consistency.
- Treat observability as part of the feature: logs, metrics, traces, alerts.
- Design for failure: retries, timeouts, idempotency, and rollback should be intentional.
- Default to secure choices for secrets, permissions, and data handling.