Purpose: define the git practices that keep the project navigable, auditable, and safe — for both human contributors and AI agents operating in parallel. These practices are not aesthetic; each one prevents a specific class of failure.
Every rule here exists because the absence of it has caused real damage. Read them as preventive measures, not preferences.
The primary branch (master, main, or whatever the project calls it — referred to below as the trunk) is protected.
These rules apply to humans and AI agents identically. No contributor is “trusted enough” to skip them — the rules exist because the failure modes are equally bad regardless of who triggers them.
Almost never. The exceptions are:
If you find yourself wanting to force-push to recover from a small problem, stop and find a non-destructive path. Reverts, new commits, and PRs are almost always available.
Branches are named by intent and content.
feature/<slug> # new functionality
fix/<slug> # bug fix
chore/<slug> # tooling, dependencies, infrastructure
refactor/<slug> # restructuring without behavior change
docs/<slug> # documentation-only changes
test/<slug> # test-only changes
release/<slug> # release preparation
add-csv-export, not add_csv_export or AddCsvExport.fix-date-parser-overflow beats date-parser.feature/bl-0428-csv-export is fine. So is feature/csv-export.In practice: feature/add-csv-export, fix/date-parser-overflow, refactor/extract-session-middleware, test/regression-for-bl-0517.
git branch -a) is a quick view of what is in flight. Cryptic names defeat this.When an AI agent (especially an autonomous loop) is allowed to propose a fix to an artifact the project treats as authoritative — methodology docs, design-system tokens, regulatory checklists, upstream spec mirrors — the fix lands on a patch branch, not directly on the trunk.
<area>-patch/YYYY-MM-DD-NN
Examples:
methodology-patch/2026-05-25-01 # first methodology patch of the day
designsystem-patch/2026-05-25-01 # first design-system patch of the day
spec-patch/2026-05-25-02 # second upstream-spec patch of the day
runbook-patch/2026-05-25-01 # first runbook patch of the day
Common area prefixes (pick from these or register your own in the project’s instruction file):
| Area prefix | Authoritative artifact |
|---|---|
methodology |
The methodology docs themselves (in a self-applying project — see Constraint 1 of templates/AUTONOMOUS_LOOP.md “Tiered autonomy”). |
designsystem |
Design tokens, component spec docs, brand guidelines. |
spec |
Upstream contract mirrors, OpenAPI specs the project ingests. |
runbook |
On-call runbooks, incident-response checklists, escalation maps. |
compliance |
Regulatory checklists, audit manifests, license inventories. |
Each project records its allowed area prefixes in CLAUDE.md / AGENTS.md (or equivalent) so the autonomous loop knows which areas it can patch and which it must escalate.
The convention enables three things:
The patch branch follows all other rules in this doc: trunk protection still applies (no force-push, no direct trunk commit), commits still have clear messages, CHANGELOG entry lands in the same commit as the patch. All DoD gates still apply — Gate 4 (documentation updated, see 07_definition_of_done.md “Gate 4 — Documentation updated”) and the diff-verification mode of Gate 5 (see 10_testing_and_verification.md “Three modes”) gate the merge.
The autonomous loop never auto-merges a patch branch — that’s always the maintainer’s gate. See AUTONOMOUS_LOOP.md “Tiered autonomy for authoritative artifacts” for the tier matrix that decides whether a finding becomes a patch branch (T0/T1) or stays as advice (T2/T3).
Commit and push after every completed sub-task once tests pass locally. Do not accumulate giant uncommitted change sets.
git bisect is used to find the introducing commit, smaller commits localize the bug more precisely.Committing broken intermediate states bloats the history with noise and breaks bisection. The discipline is: complete a coherent sub-task, run the relevant tests, commit. If a long sub-task naturally produces broken intermediate states, keep them local until the sub-task is consistent, then commit the consistent end state.
Push the commits often, too. A local-only commit:
Default to push-after-every-commit on the active branch. Branches in flight should always be visible on the remote.
Follow conventional-commits style:
<type>(<scope>): <subject>
<optional body>
<optional footer>
| Type | When to use |
|---|---|
feat |
New behavior visible to the user or an external system. |
fix |
Bug fix. The diff demonstrably corrects broken behavior. |
chore |
Tooling, deps, configuration, housekeeping. No user-visible change. |
refactor |
Restructuring code with no behavior change. |
docs |
Documentation changes only. |
test |
Tests added or changed (without changing tested behavior). |
release |
Version bumps, release-tag commits. |
perf |
Performance improvement (provable, with measurement). |
revert |
Reverts a prior commit. Body must name the reverted commit. |
A short, lowercase identifier of what part of the codebase the commit touches: (api), (web), (backlog), (deploy), (auth). Optional but useful for grep.
Closes BL-0428. Co-authored-by: ... BREAKING CHANGE: ....feat(api): add CSV export endpoint for activity report
Operators currently re-key totals manually. The new endpoint streams CSV
matching the on-screen table filter state.
Closes BL-0428.
fix(web): handle empty trip data in summary card
The card threw on `trips.length === 0` because of an unguarded `.reduce`.
Added a guard plus a regression test.
Closes BL-0517.
refactor(auth): extract session validation into shared helper
No behavior change. Three routes were duplicating the same check; now
they all call validateSession() from the shared module.
Short-body types need no ceremony: chore(deps): upgrade typescript to 5.4, docs(methodology): add 09 git workflow.
fix stuff, wip, update, more changes. They are noise..gitignore that slipped past)..env files. Never. If a secret is committed by accident, treat it as compromised: rotate immediately, then scrub history (force-push coordinated, not silent).Most modern package managers produce a lock file (package-lock.json, pnpm-lock.yaml, yarn.lock, Cargo.lock, Gemfile.lock, composer.lock, poetry.lock, uv.lock). The lock file pins exact versions across all dependencies, ensuring install produces the same tree every time.
Lock files are an underrated source of confusion in AI-collaborated workflows: agents touch dependencies, lock files change, and the resulting diffs are huge and inscrutable.
Lock files are committed to the repo. They’re not generated artifacts to gitignore. The whole point is reproducibility — the lock file is the dependency snapshot. The exception is for libraries that are themselves installed as dependencies in other projects (where the lock file would interfere with downstream consumers); applications and end-user tools commit the lock file.
| Operation | Update lock file? |
|---|---|
Adding a new dependency (npm install foo) |
✓ Yes — the new dependency requires the lock update. |
Removing a dependency (npm uninstall foo) |
✓ Yes — remove the dead entries. |
Explicit upgrade (version bump in package.json, npm update foo) |
✓ Yes — that’s the point of the upgrade. |
Unrelated work that happened to regenerate the lock file (you ran npm install just to fetch deps) |
✗ No — discard the spurious lock-file diff. |
Security patch (npm audit fix) |
⚠ Only with explicit user OK and only if scoped to the patch — security fixes can cascade unexpectedly. |
If you ran npm install for unrelated reasons and the lock file changed:
git checkout -- package-lock.json # or: git restore package-lock.json
Then re-fetch dependencies from the unchanged lock — most package managers have a “clean install” command for exactly this (npm ci, pnpm install --frozen-lockfile, yarn install --frozen-lockfile, cargo build --locked).
A massive lock-file diff usually indicates a dependency cascade — one dependency upgraded transitively forced many others to shift. Read the top of the diff to find the root cause:
package.json change you haven’t pulled; reset your working tree and pull first.An agent that runs npm install just to set up the workspace can produce a diff carrying unintended transitive bumps, conflicts with the user’s pinned versions, and hundreds of lines of noise that bury the actual work.
Defensive pattern: agents do not commit lock-file changes unless the work directly involved dependencies. When in doubt, discard the diff and reinstall from the committed lock.
Pull requests are the unit of review. Each PR should be readable, scoped, and traceable.
Two sections, always:
## Summary
- <Bullet 1: what changed and why.>
- <Bullet 2: any non-obvious decision.>
- <Bullet 3: any tradeoff worth flagging.>
## Test plan
- [ ] <Specific verification step 1>
- [ ] <Specific verification step 2>
- [ ] <Specific verification step 3>
Summary: one to three bullets on what changed and why — not a diff recap, since the diff is right there. Highlight what the reviewer would otherwise miss.
Test plan: specific, checkable verifications matching the Definition of Done gates relevant to this change — “ran the full suite locally,” “verified the CSV download at /reports respects the date filter,” “verified in both themes,” “updated CHANGELOG and README.” Vague entries (“tested it”) are not a test plan.
Every PR should close at least one BL-### item. Reference it explicitly:
Closes BL-0428.
The word matters — Closes, Fixes, Resolves — pick one and stay consistent. Many tools auto-link these.
Never include co-author credit unless explicitly agreed. Auto-adding a Co-authored-by: ... line for an AI agent is a project-by-project decision, not a default.
Multiple unrelated items (one PR closes one, or a few tightly-coupled, items); WIP commits left in; debug code (console.log, commented-out blocks); and everything on the never-commit list — secrets, generated files, unrelated lock-file churn.
GitHub (and most hosting platforms) offer three ways to land a PR onto the trunk: squash merge, merge commit, or rebase-and-merge. They produce different shapes of history and different tradeoffs.
| Strategy | What it produces | When to prefer |
|---|---|---|
| Squash merge | One commit on the trunk per PR. The PR’s individual commits are collapsed into a single commit with the PR title + body. | Most projects, most of the time. Clean linear history; easy to revert (git revert <commit>); simple git log. |
| Merge commit | The PR’s individual commits land on the trunk plus a merge commit marking the integration point. | When the PR’s individual commits are meaningful and you want to preserve them (e.g., a multi-step refactor where each step is independently valuable). Produces a branchy graph. |
| Rebase and merge | The PR’s individual commits land on the trunk as if they had been written there directly. No merge commit. | When you want per-commit granularity AND a linear history. Rewrites the PR’s commit hashes — coordinate before adopting if collaborators have pulled the PR branch. |
Mixing strategies on the same trunk produces a confusing history (some PRs squashed, some preserved, no apparent rule). Decide once per project; the project instruction file should name the choice (“This project uses squash-merge for all PRs to main.”).
Squash merge is the default for most projects. The benefits (clean trunk, easy revert, simple git log) outweigh the loss of intra-PR commit granularity — which is usually noise (WIP commits, “fix typo”, “address review”) that the squash usefully discards.
Merge-commit or rebase-merge earn their place where per-commit granularity is load-bearing: monorepos generating changelogs per package, long-running PRs whose contributors want multi-commit attribution, or projects where git bisect precision matters at the commit level.
The choice changes only history shape. Review, branch protection, and conventional-commit format apply identically under all three.
The project should have hooks for linting, type-checking, and formatting. Never bypass hooks (--no-verify and equivalents) unless the user has explicitly authorized it for that specific commit.
Hooks catch the cheap-to-prevent failures: a missing import, an unformatted file, a type error. CI will also catch these, but CI runs minutes later — the local hook saves the round-trip.
The user has explicitly said “skip hooks for this commit” — usually because the hook itself is broken and fixing it is being separately tracked. In that case:
--no-verify (or equivalent) for that one commit.The agent never decides to bypass on its own. If a hook is failing repeatedly and seems broken, surface the observation; let the user direct.
Once a commit is pushed to a shared branch, it is history. New commits, not --amend.
Amending a published commit changes its hash. Other contributors who have pulled the original commit now have a divergent history. The conflict is hard to resolve cleanly and easy to resolve destructively (force push, lose work).
A new commit, by contrast, advances history without rewriting it. Everyone’s local clones converge naturally.
A commit that exists only in your local working directory (never pushed) can be amended freely. The hash change has no other reference yet.
If you have a long messy local history and want to clean it before opening the PR, squash it locally. Once the PR is open, the commits inside it are part of the shared history — further history rewrites should be done with care and coordination.
git revert to produce a new commit that undoes it.When two or more contributors (humans, AI agents, automation) are working in the same checkout of the repo at the same time, dangerous shared state appears:
git checkout other-branch changes HEAD; the other contributor’s working files now point at a different branch than they think.master strands another session’s commits on a feature branch — those commits may end up referenced from nowhere and lost to garbage collection.Use git worktree to give each contributor (especially each AI agent session) an isolated working tree.
git worktree add <path> <branch>
For example:
git worktree add ../project-agent-a4f2 feature/csv-export
git worktree add ../project-agent-b193 feature/date-fix
Each session operates in its own directory. HEAD-shifts in one tree do not affect the other. Commits, branches, and config are shared (it is the same repository), but the workspace is independent.
git worktree add <path> <branch> # existing branch
git worktree add -b <new-branch> <path> <base> # new branch
git worktree list
git worktree remove <path> # clean state
git worktree remove --force <path> # DISCARDS uncommitted work
git worktree prune # directory already deleted
A recurring failure to clean up worktrees clutters the disk and the worktree list. Build cleanup into the end of the session. Note that remove --force is on the ⚠ (approval-gated) rows of the operation table — it discards uncommitted work in that tree.
git worktree add -b <branch> <path> <base> resolves <base> against local refs, so a stale local ref gives a stale tree. Fetch first and branch from the remote-tracking ref (origin/<base>); add --no-track so a bare git pull in the new tree does not pull the base. See also what the lock guarantees.post-checkout hooks run when a worktree is created. Hooks that install dependencies or run code generation often fail in a fresh tree that lacks their environment, and git worktree add then exits non-zero although the tree was created and is usable. Do not read the exit code as “creation failed”; re-run the hook once the environment is set up.git worktree lock guards only against git itself. It blocks git’s own prune, move and remove unless forced twice; it does not stop anything from running unlock, and worktree managers commonly unlock before their own removals — yours included.git worktree move <path> <holding-dir> rather than removing it. A move can be undone; remove --force cannot. If the tree is locked, someone has claimed it — ask rather than forcing.Do not create filesystem links from inside a worktree to the parent repo’s directories (especially package directories like node_modules). When the worktree is removed, the link is followed and the target is emptied. This has happened in real projects and is brutally destructive.
If you need shared installation state (e.g., a shared package cache), use the package manager’s native shared-cache feature, not filesystem links.
One table, three autonomy levels, matching the destructive classes in 11. ✓ = do it without asking. ⚠ = approval-gated: surface the intent and wait for an explicit, scoped yes — then the agent may execute. ✗ = agent-prohibited: a human executes, always; the note says what the operation costs.
Pairs with the decision-ownership matrix in 11_human_roles.md, which covers decisions broadly. This table is git-operation-specific.
| Operation | Agent autonomy | Notes |
|---|---|---|
git status / git diff / git log / git show |
✓ | Read-only inspection. Anytime. |
git add <specific files> |
✓ | Staging known files. Prefer this over git add . which can stage unintended files. |
git commit (on a feature branch) |
✓ | The agent’s normal flow per commit cadence. |
git push origin <feature-branch> |
✓ | Pushing the agent’s own feature branch. |
git fetch / git pull --ff-only |
✓ | Idempotent sync (not strictly read-only — pull --ff-only updates local refs and the working tree, but only if a fast-forward is possible; the flag prevents accidental merges). |
git checkout <branch> (in a worktree the agent owns) |
✓ | In its own worktree, not the user’s primary checkout. |
git worktree add / git worktree remove (own, clean) |
✓ | Manage agent’s own isolation. remove --force is ⚠ — see below. |
git merge <other> (into agent’s own feature branch) |
✓ | Bringing trunk into feature is normal. |
git rebase <upstream> (own feature branch, not pushed-and-shared) |
✓ | Rewriting your own un-shared history is fine. |
gh pr create / gh pr comment / gh pr view |
✓ | Standard PR workflow on agent’s own branch. |
gh issue view / gh issue comment |
✓ | Read + non-destructive write on issues. |
git tag -a <annotated> (release tag) |
⚠ | Only with the user’s explicit OK for this release. Tags are durable references. |
git push origin <tag> |
⚠ | Same. Tag pushes are public. |
gh pr merge (squash / merge / rebase) |
⚠ | Merging to main is consequential; user-led decision. |
gh release create |
⚠ | Release pages are durable; user-authorized. |
git rebase --onto |
⚠ | Rewrites commits onto a new base; can lose commits if mishandled. |
git push --force |
✗ | Overwrites the remote branch. Work others based on the previous version becomes orphaned. |
git push --force-with-lease |
✗ | Safer than --force but still rewrites; safe only if no one else has pushed since you pulled. |
git reset --hard |
⚠ | Discards uncommitted changes and resets HEAD. Local-only work is gone. |
git checkout -- <file> / git restore <file> / git checkout . |
⚠ | Discards uncommitted changes in the working tree. |
git clean -fd / -fdx |
⚠ | Removes untracked files — and with -x, ignored files too, including .gitignored contents. |
git branch -D |
⚠ | Force-deletes a branch even if unmerged; its commits may become unreachable. |
git worktree remove --force |
⚠ | Removes a worktree along with its uncommitted changes. |
git filter-branch / git filter-repo |
✗ | Rewrites history. Heavy-handed; usually for secret-scrubbing. Always user-led. |
rm -rf <repo> |
⚠ | Removes the working tree. |
| Any direct push to the trunk | ✗ | Trunk lands via PR only. See Branch protection. |
Production deploys (./deploy prod, etc.) |
✗ | See Production deploys. |
approval-gated)git reset --hard origin/main; this will discard your local uncommitted changes. Confirm?” — then wait for an explicit yes. With that yes, the agent may run it.branch -D. An approval obtained for an operation that was not needed is still an avoidable loss.agent-prohibited)git reflog shows recent HEAD movements, and lost commits usually live there until garbage collection. Your hosting platform’s mirror may hold the lost remote state. The longer you wait, the lower the chance of recovery — move fast on recovery, slow on the original destructive command.
Reversibility maps to autonomy. If an operation can be cleanly undone (a revert, a new commit, a reset of the agent’s own feature branch), the agent can do it. If it can’t be cleanly undone (force-push, hard reset, history rewrite, branch deletion, production deploy), the agent waits for the user.
This is the same threshold the decision-ownership matrix in 11_human_roles.md uses — reversibility is the dominant factor in deciding what belongs to the human vs. the agent.
When the table says ⚠ “only with the user’s explicit OK,” the agent doesn’t infer the OK from prior conversation. The agent surfaces the intent (“I’m about to cut release v1.2.3 — tag, push, and create the GitHub release page. Confirm?”) and waits for an explicit yes. The OK applies to that operation, not to future operations of the same kind.
If the project has a production deploy path — a script, a CI workflow, a CLI command — AI agents never run it autonomously. Production is the user’s domain.
This applies regardless of permissions. Even if the agent has the technical capability to run deploy.sh production, it does not. Production deploys carry consequences that an AI cannot fully evaluate: customer impact, data implications, timing relative to incidents, regulatory considerations.
A bad production deploy can:
None of these is recoverable by reverting code. They require operational response.
Name the command in the project instruction file, with the prohibition attached to it rather than filed elsewhere — “Production deploy: scripts/deploy.bat production. This command is user-only; AI agents and automated workflows never run it. Staging: scripts/deploy.bat staging.” The warning is unsubtle on purpose: a contributor reading the command learns it is off-limits in the same breath.
What the agent cannot do is push the button. The button is the human’s.
Commits accumulate continuously; releases are deliberate snapshots. The release-tag commit makes a specific point in history easy to refer back to, easy to deploy from, and easy for adopters to pin against.
Three reliable triggers:
What NOT to release: every commit, every WIP push, every small refactor. Releases should be referenceable.
Use SemVer 2.0: MAJOR.MINOR.PATCH.
| Bump | When to use |
|---|---|
| MAJOR (X.0.0) | Breaking change. Adopters who pinned to the previous major need to actively migrate. For a methodology-style project: a renamed doc, a removed section, a restructure of artifacts adopters link to. For code: a breaking API change. |
| MINOR (X.Y.0) | New feature, new section, new artifact — backward-compatible. Adopters who upgrade get more; nothing they depend on breaks. |
| PATCH (X.Y.Z) | Fix that doesn’t change the surface. Typo, link repair, factual correction, doc clarification. |
A useful heuristic: if an adopter who pinned to the previous version would have to do work to upgrade, it’s at least a MINOR; if they’d have to change their existing usage, it’s a MAJOR.
A single commit per release on the trunk. The conventional-commit type is release:, no scope needed:
release: vX.Y.Z — <short summary, under 70 chars>
<Body summarizing what's in the release, grouped by category
(Feat / Fix / Docs / Chore). Bullet style with file references.>
CHANGELOG renamed [Unreleased] -> vX.Y.Z in this commit.
The body should match the corresponding CHANGELOG entry — they’re describing the same thing for two different audiences (git readers vs. CHANGELOG readers).
git tag -a vX.Y.Z -m "vX.Y.Z - <same short summary as commit subject>"
Annotated tags carry the tagger’s identity, timestamp, and message. Lightweight tags (git tag vX.Y.Z) are just refs to a commit, with no metadata. Annotated tags are the standard for releases. If your project signs tags (git tag -s), use that; otherwise unsigned-annotated is the baseline.
Two pushes, never combined — git push origin main (the release commit), then git push origin vX.Y.Z (the tag). --follow-tags works if configured, but the two-step form makes the order visible and intentional.
A release entry that states a live repository total in the present tense — “the repo now has N tags for N releases” — is false the moment the release’s own tag is pushed, because it was counted before that tag existed. The same is true of any figure the release itself changes: file counts, line totals, doc inventories.
Two rules, both cheap:
44 tags as of the release commit rather than 44 tags. For anything the release increments, state the arithmetic instead of the total: “N prior tags; this release’s annotated tag makes N+1.”Corrections go forward, in a later entry — a published CHANGELOG entry is history and is not rewritten. The failure mode this prevents is specific and recurring: each release corrects its predecessor’s total, states its own the same way, and is wrong again one release later.
Same content, two audiences: CHANGELOG.md is the canonical, durable, structured record; platform release notes are polished for browsing and can carry media the CHANGELOG can’t. Generate the notes from the CHANGELOG entry — never write them twice. Shorter notes that link to the CHANGELOG for detail are fine.
The normal flow is: item → branch → PR → merge → eventual release. A hot-fix bypasses parts of this for genuine emergencies.
The bar should be high. Reserve hot-fixes for:
Do NOT hot-fix for: ordinary bugs, feature requests, “while we’re at it” improvements. Those go through the normal flow.
main:
git checkout -b fix/hotfix-<slug> v<last-released-version>
Branching from main would include unreleased work that hasn’t been verified for the production environment. Branching from the tag isolates the hot-fix to a known-good base.
Scope ruthlessly. The hot-fix touches only what’s needed to resolve the immediate problem. Defer everything else — even adjacent improvements — to follow-up items.
Test the fix per the DoD. Status: done requires Test: pass — hot-fixes do not skip this. The verification loop is faster (because the scope is tight), not absent.
File the item with Priority: P0 so the bypass-of-normal-flow is visible in the backlog. The item closes in the same hot-fix PR.
Merge the hot-fix branch to main via PR. The PR is reviewed under hot-fix urgency (faster than normal, not skipped).
Cherry-pick to release branches if you maintain any. If your project has long-lived release branches (e.g., release/v1.x), the hot-fix needs to land on each affected one: git cherry-pick <commit-hash>.
Tag a new patch release (see Release tagging). The hot-fix gets its own version so adopters can pin to “the version that has the fix.”
ARCHIVE.md — or to a dedicated epics/E00-hotfixes/ epic if your project sees enough hot-fixes to warrant one.After the hot-fix lands, the AI-collaborated equivalent of a post-mortem:
This is a strong memory-entry candidate (see 08_lessons_and_memory.md “When to write a new memory” — Trigger 4: validated judgment call after an incident).
Merge conflicts are normal. Resolving them carelessly is not.
Prefer resolving merge conflicts to discarding work. Investigate whose change is whose before picking a side.
git checkout --theirs / --ours without reading. You may discard real work or keep stale work without realizing.If the merge conflict is so large that you don’t know how to resolve it, stop and surface the situation. A 30-file conflict between two long-lived branches is usually a sign that branches diverged too long; the right answer is a coordinated re-merge, not a heroic single-contributor reconciliation.
Git history is the project’s permanent record. Every commit, every PR, every backlog state change is preserved.
git log -p -- <file> gives every change to a file with diffs (why a decision was made); git blame <file> finds the commit behind a specific line; git log --grep="<pattern>" finds every commit for an item or topic; git log --all --source can reconstruct work on deleted branches.
Every change to a BACKLOG.md or ARCHIVE.md — adding an item, flipping a status, acquiring or releasing a lock — is a git commit. This makes the backlog itself fully auditable. See 05_locks_and_parallel_work.md for the lock audit pattern.
Commit messages are documentation read months later by people without your context; a messy history is a permanent tax on every future reader; and in an incident, git history is usually the first source consulted.
Most real projects have operational concerns beyond writing code: deploys, data pipelines, scheduled jobs, monitoring, on-call response. The methodology covers code workflow; operational workflow is project-specific. But certain patterns transfer.
| Pattern | Why it matters |
|---|---|
| Document the deploy command(s) in the project instruction file. | Future contributors (and the AI) need to know how to ship. |
Mark which commands are agent-prohibited (a human runs them). |
Production deploys, destructive backfills, data resets — these touch real user state, so a user’s approval authorizes the human to proceed, not the agent to act. |
| Backup before any destructive operation. | A backup taken five minutes before a wrong-button-press is the cheapest recovery. |
| Smoke-test after deploy. | A successful deploy that doesn’t smoke-test isn’t proven to have moved real state. |
| Have a runbook for common ops. | Restarting a service, rotating a credential, rolling back a release — write the steps down so they can be followed under pressure. |
| Separate dev / staging / production environments cleanly. | The boundary between these is where most operational incidents happen. |
| Idempotent batch jobs. | A re-run of a stuck job should produce the same end state, not corrupt it. |
| One-way data flows where possible. | If content syncs from dev to prod, never the reverse, the failure modes shrink. |
Operational work is normal backlog work: “rotate the third-party API key,” “investigate Tuesday’s 500 spike,” “build the DR runbook.” Items and epics follow the usual lifecycle — the Test: field just means something operational (“the rotation completed and the smoke test passed”). Adapt the DoD gates to the operation; verifiable completion is the invariant.
The methodology deliberately doesn’t pick your deploy tooling, monitoring stack, incident procedures, or runbook format. It asks you to have them.
If your project has production state (real users, real data, real money), destructive operations against it are agent-prohibited — a human executes them, and an approval does not move that step to the agent. Automation is fine for backups, deploys to staging, and scheduled jobs that don’t destroy state. Automation that drops tables, rolls back data, or cancels payments needs a human in the loop, not a confirmation prompt in front of a robot.
| Mistake | Fix |
|---|---|
| Direct commit to the trunk for “a small thing.” | Open a PR. Small PRs land quickly. The exception is not worth the precedent. |
| Force-pushed to the trunk after a bad merge. | Recover via reflog and your hosting platform’s branch backup. Resolve to never do this again. |
| Generic commit messages (“wip,” “update,” “fix”). | Rewrite locally before pushing. After pushing, follow up with better messages or a clean revert+redo. |
| Long-running branch is many commits behind the trunk. | Pull the trunk into the branch regularly. Conflicts grow non-linearly with divergence. |
| Hooks bypassed without explanation. | Bring the bypass to surface. Either fix the underlying issue or file an item for the hook itself. |
| Stale branches lingering on the remote for months. | Periodic cleanup: delete merged branches. Keep unmerged in-progress branches; archive abandoned ones with a tag. |
| PR with no test plan. | Add one. Specific, checkable verifications. The DoD requires it. |
| PR closes an item but doesn’t reference it. | Add Closes BL-###. so the tooling and humans both make the connection. |
| Co-author trailers added without permission. | Remove them. Authorship is consensual. |
AI agent ran git reset --hard on the user’s working tree. |
Surface and confirm before destructive operations. Recover via reflog if possible. |
| Production deploy run by automation that wasn’t supposed to. | Hard rule violation. Audit how it happened; restore boundary. |
The git workflow rules bind every contributor — human or AI. They are not aesthetic preferences; they are preventive measures.
When a user explicitly directs a workflow exception (e.g., “force-push this to recover from yesterday’s bad merge”), the exception applies to that specific operation, not to the rule generally. The next operation reverts to the standard rules unless the user states otherwise.
When a contributor (human or AI) is uncertain whether an operation is allowed, the safe path is to ask. The cost of a question is small; the cost of an unintended destructive operation can be large.
Destructive operations are the most-protected category, and they split in two. Workspace-destructive operations are approval-gated: never autonomous, and the agent may execute after an explicit, scoped, per-operation yes. Production-destructive operations are agent-prohibited: a human executes, with care and a recovery plan, and consent does not transfer that step. The classes are defined once in 11_human_roles.md.