Skip to content

Git Workflow

Consumers: commit-assembly step of /maigo:go, /maigo:quick, /maigo:team, /maigo:address-comments; the orchestrator generally whenever it runs git commands or briefs an agent that will.

Why this skill exists

Committing a scoped change and running git commands correctly across worktrees are mechanical steps that are easy to get subtly wrong: staging too broadly, mutating shell state with cd, fixing a CI failure on the wrong branch, or reasoning about diff size against the wrong baseline. None of these mistakes fail loudly at the time — they surface later as unrelated noise in a PR, a confusing force-push, or a wrong claim in a summary. This skill collects the conventions that keep git operations scoped to exactly what was intended.

Scoping and staging

Stage explicit files, never git add -A

When committing a scoped change, stage the specific files by path. Do not use git add -A / git add . — the working tree often carries regenerated or drift files (lockfiles, generated dependency manifests, build caches) that a blanket add will sweep into the commit as unrelated noise.

How to apply: run git status first and read it. Stage only the intended paths: git add <file1> <file2>. Be especially careful after a rebase, a package-manager sync/install step, or after a delegated subagent ran — those can leave unexpected generated changes in the working tree.

Treat drift as drift, not just "don't stage it" — revert it. A package-manager install/codegen step (backend or frontend) can silently rewrite a lockfile's security-relevant sections (e.g. deleting a version overrides:/pin block) or scaffold a placeholder config file, in addition to ordinary dependency-drift noise. After any such step, run git diff HEAD --stat and scan for lockfiles, workspace-config files, and other generated artifacts; git checkout HEAD -- <file> (or delete a scaffold file) anything not intentional. Leaving it unstaged still risks a later blanket add or a teammate sweeping it in — and a dropped security pin is a real regression, not just noise.

Don't cd; use absolute paths

Never cd into worktrees or other directories when running git/bash commands or briefing another agent. Always:

  • Use absolute paths for file arguments.
  • Use git -C <worktree-path> for git commands targeting a different worktree.
  • Use tool-specific working-directory flags when available (e.g. pytest --rootdir, a package manager's --project/--cwd flag).
  • When briefing another agent that will run commands: state the working directory as context ("Working dir: /abs/path/to/worktree") but don't hand it a cd <path> && <cmd> instruction — the agent should use absolute paths or git -C directly.

Why: cd mutates shell state, complicates tool composition, and most harnesses already handle per-tool working directory cleanly via absolute paths or -C-style flags.

Exception: if a human explicitly asks to cd somewhere, do it. Otherwise default to no-cd.

This matters most in multi-worktree setups, where several sibling worktrees exist off the same upstream and it's easy to accidentally run a command against the wrong one after a cd.

Commit assembly (amend vs. new commit vs. which branch)

Fold polish into the unreleased introducing commit

When the underlying feature commit on a local branch has never been released, fold review-driven polish — parameter renames, docstring updates, perf nits, additional tests — into the introducing commit via git commit --amend --no-edit (or --amend with an updated message; see commit-message) rather than keeping it as a follow-up commit.

Why: a parameter or symbol that never existed in a release doesn't need its rename preserved as a historical artifact — squashing means it's born with the right name and the rename never shows up in history. If the repo squash-merges on merge, the end PR result is identical either way, but cleaner local history helps the author's own rebase / pre-push self-review workflow.

How to apply:

  • Default to "amend into the introducing commit" framing when drafting how to land polish on a local feature branch.
  • Only suggest a separate commit when the polish is genuinely independent — different feature scope, separate user-visible change, or the introducing commit has already been published to a PR that reviewers are mid-review on.
  • Don't preserve a rename as a historical artifact when the renamed symbol was never released to users.

When polish hunks are tangled, amend into the tip — don't reconstruct

The rule above says to amend polish into the relevant introducing commit. When the polish is per-thread localized (a rename, a docstring, one new test), do that — amend into the specific commit each thread targets. But when multiple review threads all reshape the same function or loop together, splitting the polish across commits requires hand-reconstructing intermediate file states for an interactive rebase — a high-error-rate operation that doesn't survive a later force-push cleanly.

In that case, default to amending everything into the tip (most recent) of the unreleased commits, rather than reconstructing a midpoint version of the function that never existed in isolation.

How to apply: during commit-assembly planning, inspect the actual staged diff before committing to a split-amend plan. If hunks for multiple threads collide in the same function and the intermediate state isn't naturally meaningful on its own, fold everything into the tip commit instead. Surface the deviation from a split-amend plan explicitly so the plan's author (or the user) can override.

A fix belongs on the branch that surfaced it, not a fresh branch off main

When an in-flight branch (e.g. a CI-environment-upgrade PR) is itself what surfaces a new lint/type/test failure, the fix belongs on that branch (folded into the same PR), not on a fresh branch cut from the main/default branch.

Why: CI runs against the in-flight branch. A fix sitting on a separate branch does nothing for the failing CI, and the failure often only exists because of that branch's own changes (e.g. an upgraded dependency revealing a type error that didn't exist before).

How to apply: before branching off the default branch to fix something, check whether the failure was introduced by the currently in-flight branch. If yes, apply the fix there and consider folding it into the introducing commit (see "Fold polish into the unreleased introducing commit" above).

Referencing private-layer files: path only, never content

Public-repo commit messages, PR bodies, and file contents may cite a private-layer file by its path (e.g. ~/.claude/rules/model-dispatch.md) but must not copy or excerpt its content — including quoting its judgment/rule text verbatim. When the semantics need conveying, summarize in one sentence instead of reproducing the source.

Sizing a diff

Diff against the real merge-target baseline, not the branch tip

When quantifying a change's footprint — "what gets deleted", "is this a big diff", "much smaller than what" — establish the real baseline first: git diff upstream/main..HEAD (or the repo's actual merge target) and git show upstream/main:<file>. Don't reason against the current branch tip as if it were the merge target.

Why: it's easy to describe a change as "net-delete N lines of symbol" without checking whether symbol exists on the merge target at all — if the symbol was introduced by the branch's own unmerged feature commit, the "deletion" is actually reworking in-flight work, which completely changes the framing (an amend of the feature commit, not a deletion stacked on top — see "Fold polish into the unreleased introducing commit" above).

How to apply: before stating diff sizes or "what gets removed", run git rev-list --count upstream/main..HEAD and check whether the code being "deleted" actually exists on the merge target (git show upstream/main:<file> | grep <symbol>). If the branch carries unmerged feature commits, the honest comparison is "reworked commit vs. current commit" (an amend), not "vs. the merge target". Verify empirically against git show/git diff output — don't assert sizing from memory of the working tree.

Pushing and opening a PR

A widget selection is not authorization to open a PR. Never run gh pr create (including gh pr create --web, which only opens a pre-filled browser tab) unless the user explicitly asks for it in the current turn. Picking a multi-choice option like "Commit and open PR" from an AskUserQuestion-style widget is ambiguous for the PR-creation half specifically — confirm once more (plain text is fine) immediately before calling gh pr create. After committing and pushing a branch, default to stopping there: report the branch/compare URL and ask if a PR should be opened.

Worktree hygiene (references)

Ten conventions for worktree lifecycle and git-attribution — sibling-layout naming, when to open a separate worktree for a pre-existing issue, batch cleanup safety, squash-merge-aware "already merged" checks, and not over-attributing surprising git state (commits/rebases/wrong commit subject) to a rogue agent when it's more likely the user working in parallel or an unverified delegate report. Details and recipes in references/worktree-hygiene.md:

  • Sibling layout — worktrees live as siblings of the main checkout (<repo>-<topic>, branch <topic>), not nested under a tool's default path.
  • Pre-existing fix → new worktree off upstream/main, not folded into the current feature branch (proportionality counter-case for trivial diffs).
  • Batch cleanup: show the exact command list before running; don't chain many worktree/branch ops in one shell call (timeout risk).
  • Squash-merge "already merged" check needs gh search prs, not git branch --merged (squash breaks ancestry).
  • Don't over-attribute unexpected git state to a rogue agent — verify quietly (reflog, staged files) before alarming; on a delegated task, verify git state (reflog/log/status) before committing on top of it.
  • Verify the landed commit subject with git log --oneline -1 after every commit — don't trust the tool's echoed command string.
  • "Fix the commit message" on a tmp/mislabeled branch means squash the whole branch into one commit, not reword just the tip.

Read references/worktree-hygiene.md when opening/cleaning up worktrees or when git history looks surprising after a delegated task.

What this skill does NOT cover

  • Drafting the commit message text itself — see commit-message.
  • Deciding whether to commit at all, or when in a flow the commit happens (e.g. after Soyo + Taki pass) — that's the calling command's own policy.
  • Drafting the PR title / description content itself — separate skill (github-title-description). This skill only governs when it's okay to actually run gh pr create (see "Pushing and opening a PR" above).