skills/mav-git-workflow/SKILL.md
Git branching strategy, commit conventions, merge conflict handling, and branch lifecycle. Implements a simplified Gitflow with protected branches and conventional commits. Covers worktree-based multi-story workflows and cross-references stacked-PR handling.
npx skillsauth add thermiteau/maverick mav-git-workflowInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A trunk-based strategy for all development work. This skill defines how branches, commits, and merges are managed.
A configured remote repository is mandatory. Local-only git is a hard fail — all projects must push to a remote hosting service (GitHub, GitLab, Bitbucket, Azure Repos, etc.).
Before starting any work, verify a remote exists:
git remote -v
If no remote is configured:
A project with no remote cannot use protected branches, pull requests, or CI/CD — all of which are foundational to this workflow.
Maverick's multi-story workflows (do-epic) run each story in
its own git worktree under .maverick/worktrees/. This keeps in-flight
stories isolated from each other and from the main checkout.
Before entering do-epic (or any flow that relies on
worktrees), verify the capability:
uv run maverick worktree list
If this fails, the workflow aborts with a hard error. Worktrees require:
git rev-parse --is-shallow-repository returns
false). Shallow clones cannot create new worktrees.git worktree add (2.15+)..maverick/worktrees/.One worktree per story, created off the resolved base branch:
uv run maverick worktree create <branch> [--base <base-branch>]
On every exit path — merge, eject, abort — destroy the worktree:
uv run maverick worktree destroy <path>
Leaving stale worktrees inflates disk usage and confuses future
worktree list reads. The only reason to keep a worktree after exit is
if a PR was ejected and the human may want to inspect the in-flight
state — note the path in the eject comment.
A freshly created worktree shares the main checkout's .git, but not
its dependency installs, generated files, or untracked secrets. Many repos
need to materialise those before the first build can run inside the new
worktree (e.g. pnpm install, pip install -e ., symlinking .env).
If .maverick/config.json declares hooks.worktree_post_create, the CLI
runs that script after git worktree add succeeds. The hook receives:
$1 — absolute worktree pathMAVERICK_WORKTREE_PATH — same as $1MAVERICK_BRANCH — newly created branch nameMAVERICK_BASE_BRANCH — branch the new branch was forked fromMAVERICK_REPO_ROOT — main checkout's absolute pathA non-zero exit, missing script, or non-executable script causes
maverick worktree create to exit non-zero with a clear error. The
worktree is left on disk so an engineer can inspect what the hook
produced — clean up with maverick worktree destroy once diagnosed.
Example wiring (.maverick/config.json):
{
"hooks": {
"worktree_post_create": "scripts/worktree-setup.sh"
}
}
The hook is optional. Repos that don't declare one see exactly today's behaviour. The Maverick CLI stays language-agnostic — the hook can be any executable.
If a story depends on a sibling story whose PR is still open, stack per
mav-stacked-prs. The worktree is created off the sibling
branch instead of the default branch. The retarget guard in
mav-stacked-prs handles the sibling merging.
The branching layout is driven by the per-project config in
.maverick/config.json → git_workflow. Resolve the key branches
with the CLI:
STORY_BASE=$(uv run maverick git-workflow story-base) # branch new work off this
PR_TARGET=$(uv run maverick git-workflow pr-target) # gh pr create --base target
Example layout (trunk-based, story_base = main):
stable (what end users clone; CI-managed; fast-forwarded to the latest release tag)
main (trunk — carries -dev between releases; tagged at each release)
├── feat/42-add-export
├── fix/57-login-crash
├── chore/63-update-deps
└── release/<version> (short-lived; exists only long enough to cut a release)
Example layout (Gitflow-style, story_base = develop):
main (production; tagged at each release)
develop (integration — carries -dev between releases)
├── feat/42-add-export
├── fix/57-login-crash
└── chore/63-update-deps
| Branch | Purpose | Who commits | Protected |
|---|---|---|---|
| stable (if present) | What end users install — always points at the most recent release tag | Fast-forwarded by CI only; never by humans | Yes (CI-only) |
| Default branch (e.g. main) | Trunk or production branch — tagged at each release | Merge from story/release branches via PR only | Yes |
| Story base (e.g. develop, or same as default) | Integration branch feature branches are created from and PR'd back to | Merge from feature/fix/release branches via PR only | Yes |
| Feature/fix/chore branches | Active development | Claude Code and developers | No |
| release/<version> | Short-lived release prep — bumps version, updates CHANGELOG | Created by release scripts | No |
$STORY_BASE and PR'd back to $PR_TARGET.--base $PR_TARGET explicitly when creating PRs via gh pr create — do not rely on the repository's default branch setting, which may differ from the PR target.Format: <type>/<issue-number>-<short-description>
Resolve the branch prefix from the project config:
PREFIX=$(uv run maverick git-workflow branch-prefix <label>)
The CLI maps issue labels to branch prefixes per the project's
git_workflow.branch_prefixes config. Default mapping when not
overridden:
| Label or keyword | Branch prefix |
|---|---|
| bug, fix, defect | fix/ |
| feature, enhancement | feat/ |
| docs, documentation | docs/ |
| refactor, tech-debt | refactor/ |
| chore, maintenance, deps | chore/ |
| test, testing | test/ |
| Default (when unclear) | feat/ |
Good: feat/42-add-rubric-export, fix/57-prevent-login-crash
Bad: feature/issue-42-implement-the-new-rubric-export-feature-for-teachers
digraph branch {
"Identify base branch" [shape=box];
"Pull latest" [shape=box];
"Derive branch name" [shape=box];
"Create branch" [shape=box];
"Verify clean state" [shape=diamond];
"Stash or commit first" [shape=box];
"Identify base branch" -> "Pull latest";
"Pull latest" -> "Verify clean state";
"Verify clean state" -> "Derive branch name" [label="clean"];
"Verify clean state" -> "Stash or commit first" [label="uncommitted changes"];
"Stash or commit first" -> "Derive branch name";
"Derive branch name" -> "Create branch";
}
The base branch comes from the project config — resolve it via the CLI:
STORY_BASE=$(uv run maverick git-workflow story-base)
This reads git_workflow.story_base from .maverick/config.json
(default: main). Do not auto-detect or hard-code the branch — the
config is the single source of truth, written once by maverick init.
Any workflow that reads from the main checkout before opening a
worktree — typically the design / planning subagents in
do-issue-solo / do-issue-guided / do-epic Phase 1 — must
refresh the local base branch against origin first. A stale local
base lets pre-resolved ambiguities slip into the analyst's design
pass and surfaces as phantom blockers (#102).
The pattern advances the local ref without forcing the user out of whatever branch they happen to be on:
STORY_BASE=$(uv run maverick git-workflow story-base)
git fetch origin "$STORY_BASE"
CURRENT=$(git rev-parse --abbrev-ref HEAD)
if [ "$CURRENT" = "$STORY_BASE" ]; then
git pull --ff-only origin "$STORY_BASE"
else
# Update the local ref directly; safe because $STORY_BASE is not the
# checked-out branch (git refuses to fetch onto the current branch).
git fetch origin "$STORY_BASE:$STORY_BASE"
fi
Both forms refuse a non-fast-forward — that means the local base has commits that are not on origin, which violates trunk-based discipline. Halt and report to the user; do not auto-rebase or force-update.
git checkout $STORY_BASE && git pull origin $STORY_BASE && git checkout -b $BRANCH_NAME
All commits must follow the Conventional Commits specification.
<type>(<optional scope>): <description> (#<issue-number>)
| Type | When to use |
|---|---|
| feat | New feature or functionality |
| fix | Bug fix |
| docs | Documentation only |
| refactor | Code change that neither fixes a bug nor adds a feature |
| test | Adding or updating tests |
| chore | Build process, dependencies, tooling |
| style | Formatting, whitespace (no logic change) |
| perf | Performance improvement |
| ci | CI/CD configuration changes |
(#42)feat: add rubric export endpoint (#42)
fix: prevent crash when rubric is empty (#57)
test: add integration tests for grading service (#42)
refactor: extract token cost calculation into utility (#63)
chore: update vitest to v3.1 (#71)
Commit and push after every task, not only at end of story. This
makes work durable across machine death: a takeover instance can fetch
the branch from origin and resume at the next unchecked task. See
mav-durability-on-gh for the rationale and the worked
pattern.
For commits that need more context:
feat: add rubric export endpoint (#42)
Adds a new GET /api/assessments/:id/export endpoint that returns
the assessment rubric as a downloadable CSV file. Includes proper
content-type headers and filename disposition.
digraph conflicts {
"Conflict detected?" [shape=diamond];
"Trivial conflict?" [shape=diamond];
"Auto-generated files only?" [shape=diamond];
"Resolve automatically" [shape=box];
"Flag for human intervention" [shape=box];
"Continue working" [shape=box];
"Conflict detected?" -> "Trivial conflict?" [label="yes"];
"Conflict detected?" -> "Continue working" [label="no"];
"Trivial conflict?" -> "Auto-generated files only?" [label="yes"];
"Trivial conflict?" -> "Flag for human intervention" [label="no"];
"Auto-generated files only?" -> "Resolve automatically" [label="yes — lock files, generated code"];
"Auto-generated files only?" -> "Flag for human intervention" [label="no"];
"Resolve automatically" -> "Continue working";
}
Only resolve conflicts in auto-generated files where the resolution is unambiguous:
package-lock.json, pnpm-lock.yaml, yarn.lock) — regenerate with pnpm install / npm installFlag all other conflicts for human review. Report clearly:
# Show conflicting files
git diff --name-only --diff-filter=U
# Show the conflict markers in a specific file
git diff -- path/to/file.ts
Never:
After a pull request is merged, clean up the feature branch.
# Switch back to the base branch
git checkout $BASE_BRANCH && git pull origin $BASE_BRANCH
# Delete the local feature branch
git branch -d $BRANCH_NAME
Use -d (not -D) — this will refuse to delete if the branch has unmerged changes, which is a safety check.
GitHub can be configured to auto-delete branches after PR merge. If not:
git push origin --delete $BRANCH_NAME
For cleaning up stale local branches that have been merged:
STORY_BASE=$(uv run maverick git-workflow story-base)
# List merged branches (excluding the story base)
git branch --merged $STORY_BASE | grep -vE "^\*|$STORY_BASE"
# Delete them
git branch --merged $STORY_BASE | grep -vE "^\*|$STORY_BASE" | xargs git branch -d
When your feature branch falls behind the base branch:
# Preferred: rebase onto latest base (clean history)
git fetch origin && git rebase origin/$BASE_BRANCH
# Alternative: merge base into feature (preserves history)
git fetch origin && git merge origin/$BASE_BRANCH
Use rebase for branches with only your own commits. Use merge if the branch has been shared or has a complex history. If uncertain, ask the user.
Before any branch operation, verify the working tree is clean:
git status --porcelain
If there are uncommitted changes:
git stash push -m "WIP: description"development
--- name: do-test description: Write or update tests for a code change. Operates in two modes: `unit` (module-scoped, fast, deterministic) and `integration` (crosses module / service / database boundaries). Intended to be invoked once per testable change from inside a do-issue-* or do-epic phase. Mode is required. argument-hint: mode: unit or integration user-invocable: true disable-model-invocation: false --- **Depends on:** mav-bp-unit-testing, mav-bp-integration-testing, mav-local-verificati
development
Implement a focused code change. Use this skill as the wrapper for any implementation work so the Maverick workflow report captures what was done and so the agent applies the project's coding standards before editing. Intended to be invoked once per task from inside a do-issue-* or do-epic phase, not standalone.
testing
How to stack a PR on top of an unmerged sibling branch, and how to retarget it to the repo's default branch once the sibling merges. Prevents orphan-merge incidents when a dependent story is ready before its parent.
development
Claim, lease, heartbeat, and release protocols for when multiple Claude Code instances may act on the same issue or epic concurrently. GitHub labels and marker comments are the coordination surface; local state is a cache.