Files
PiCloud/docs/git-workflow.md
MechaCat02 b8b544816d chore: initial scaffold — workspace, docs, blueprint
Sets up the PiCloud monorepo as a Cargo workspace organised around the
three-service architecture (manager / orchestrator / executor), each
backed by a *-core library crate so the same logic powers both the MVP
all-in-one `picloud` binary and the future split-process cluster mode.

  * crates/shared, executor-core, orchestrator-core, manager-core
    define the library surface and trait seams between the three
    services (`ExecutorClient`, `ScriptResolver`, `ScriptRepository`).
  * crates/picloud is the MVP entrypoint; serves /healthz on 8080
    (override via PICLOUD_BIND).
  * crates/picloud-{manager,orchestrator,executor} are skeleton
    binaries that keep the crate boundaries honest until cluster
    mode is built out in v1.3+.
  * docs/git-workflow.md defines the trunk-based workflow:
    short-lived branches, Conventional Commits, separate hotfix
    flow with mandatory reproduction tests.
  * CLAUDE.md captures the working rules for future Claude sessions.

Workspace passes `cargo fmt`, `cargo clippy -D warnings` (with
pedantic enabled), and `cargo test --workspace`. The all-in-one
binary responds on `/healthz` and `/`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 23:16:32 +02:00

203 lines
8.4 KiB
Markdown

# Git Workflow
PiCloud uses **trunk-based development**: a single long-lived branch (`main`) that is always deployable, with short-lived branches for changes that need review.
This document defines how branches, commits, reviews, and releases work — and how bug fixes flow through the system.
---
## 1. Branches
| Branch | Purpose | Lifetime |
|--------|---------|----------|
| `main` | The trunk. Always green, always deployable. | Permanent |
| `feat/<topic>` | New feature or non-trivial change. | ≤ 2 days |
| `fix/<topic>` | Bug fix (non-critical). | ≤ 1 day |
| `hotfix/<topic>` | Critical bug fix that needs to ship now. | Hours |
| `chore/<topic>` | Refactor, deps update, CI tweak, docs. | ≤ 1 day |
| `release/vX.Y` | Long-lived release branch for back-porting hotfixes to a shipped version. | Per minor version, once we tag releases |
**Rules:**
- Branch directly off `main`. Never branch off another feature branch.
- Keep branches short. If a branch lives longer than 2 days, you're doing too much in one go — split it.
- Branch names use `kebab-case` after the prefix: `feat/script-crud-endpoints`, `fix/executor-timeout-leak`.
- Delete the branch after merge.
---
## 2. Commits
**Format: Conventional Commits.**
```
<type>(<scope>): <subject>
<body — optional, wrapped at 72 chars>
<footer — optional: BREAKING CHANGE, issue refs>
```
**Types:** `feat`, `fix`, `chore`, `refactor`, `docs`, `test`, `perf`, `build`, `ci`.
**Scopes (current crates / areas):** `executor-core`, `orchestrator-core`, `manager-core`, `shared`, `picloud`, `dashboard`, `caddy`, `compose`, `ci`, `docs`.
**Examples:**
```
feat(executor-core): add operation budget enforcement
fix(orchestrator-core): release Postgres pool guard before dispatch
chore(ci): pin Rust toolchain to 1.92.0
```
**Rules:**
- One logical change per commit. If you can't describe it in one line, split it.
- Subject line: imperative, ≤ 72 chars, no trailing period.
- Body explains *why*, not *what* — the diff already shows what.
- No `WIP`, `fixup`, or `oops` commits on `main`. Squash or rewrite before merging.
---
## 3. The Feature Loop
```
┌─────────────────────┐
│ Pull latest main │
└──────────┬──────────┘
┌──────────▼──────────┐
│ Branch: feat/<name> │
└──────────┬──────────┘
┌──────────▼──────────┐
│ Commit small steps │◄────┐
│ Run tests locally │ │
└──────────┬──────────┘ │
│ │
┌──────────▼──────────┐ │
│ Open PR to main │─────┘ (iterate on feedback)
│ CI must be green │
└──────────┬──────────┘
┌──────────▼──────────┐
│ Squash-merge to main│
│ Delete branch │
└─────────────────────┘
```
**Before opening a PR:**
```sh
git fetch origin
git rebase origin/main # keep history linear
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --workspace
```
**PR requirements:**
- CI green (fmt, clippy, tests, build).
- At least one approving review for non-trivial changes. Solo dev exception: self-review the diff explicitly before merging.
- PR description names *why* and links to any issue.
- Squash-merge by default → one commit per PR on `main`.
---
## 4. Bug-fix Workflow
Bugs are classified by severity:
### Non-critical bug — normal flow
1. Branch `fix/<topic>` from `main`.
2. Write a failing test that reproduces the bug.
3. Fix it. The test now passes.
4. PR → review → squash-merge.
### Critical / production hotfix
```
┌──────────────────┐
│ Bug reported │
└────────┬─────────┘
┌───────────▼───────────┐
│ Reproduce locally │ ← required before any fix
│ Write failing test │
└───────────┬───────────┘
┌───────────▼───────────┐
│ Branch hotfix/<name> │
│ from main │
└───────────┬───────────┘
┌───────────▼───────────┐
│ Minimal fix only │ ← no refactors, no unrelated changes
│ Test passes │
└───────────┬───────────┘
┌───────────▼───────────┐
│ Fast-track review │
│ Merge to main │
└───────────┬───────────┘
┌───────────▼───────────┐
│ Tag patch release │ (vX.Y.Z+1)
│ Deploy │
└───────────┬───────────┘
┌───────────▼───────────┐
│ Cherry-pick to │ ← only if old releases are
│ release/vX.Y if active│ still in use
└───────────────────────┘
```
**Critical hotfix rules:**
- A hotfix fixes one bug. Nothing else. No "while I'm here" cleanups.
- The reproduction test stays in the suite forever — it's our guarantee the bug doesn't return.
- If the fix can't be minimal (e.g. requires an architectural change), ship a *mitigation* as the hotfix (feature flag off, rate limit, revert), then schedule the real fix as a normal feature.
---
## 5. Keeping `main` Green
Trunk-based only works if `main` is always green. Mechanisms:
- **CI gate:** PRs cannot merge unless CI passes (`fmt`, `clippy -D warnings`, `cargo test --workspace`, dashboard build).
- **Feature flags:** Code for incomplete features lives on `main` behind a flag, off by default. We finish features in small mergeable slices, not in long-lived branches.
- **Reverts are cheap:** If something slips through and breaks `main`, revert first, debug after. `git revert <merge-sha>` and ship.
- **No force-push to `main`.** Ever.
---
## 6. Releases
Until we ship publicly we deploy off `main` continuously. Once we have external users:
- Tag releases on `main`: `v0.1.0`, `v0.1.1`, `v0.2.0` (SemVer).
- Patch releases (`v0.1.1`) come from hotfixes; bump the patch number on each fix.
- Minor releases (`v0.2.0`) come from accumulated features on `main`.
- For each minor version supported in the wild, create `release/v0.1` and cherry-pick patches there. Otherwise don't bother — single trunk is simpler.
**Tagging:**
```sh
git tag -a v0.1.0 -m "v0.1.0 — MVP"
git push origin v0.1.0
```
---
## 7. What We Do Not Do
- **No GitFlow.** No `develop`, no `release` branches by default, no `next`. They add merge overhead without buying anything at our scale.
- **No long-lived feature branches.** If a feature is too big to land in 2 days, split it. Use a feature flag.
- **No merge commits on `main` from feature branches.** Squash-merge keeps history linear and bisectable.
- **No commits straight to `main`** — even for typo fixes. Go through a PR (it can take 30 seconds to review and merge).
- **No `--no-verify`** to bypass hooks. If a hook fails, fix the underlying issue.