Git workflow
Skill event4u-app/agent-config/dist/agent-src/skills/git-workflow
Universal AI Agent OS — audited skills, governance rules, replayable state. One contract, every host agent.
npx -y skills add event4u-app/agent-config --skill git-workflowAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 7 stars7 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when working with Git — branch naming, commit messages, PR creation, rebasing, or the code review process — even when the user says 'push this' or 'merge the branch' without naming Git.
SKILL.md
14.8 KB, ~3.7k tokens by cl100k_base, as published. Nobody here has run it
git-workflow
When to use
Use when preparing PRs, finishing branches, or following the team's Git workflow.
Do NOT use when:
- Code writing or review (use
php-coderorcode-reviewskill) - CI/CD pipeline changes (use
github-ciskill)
Live remote state first — never from memory
BEFORE ANY MERGE / PUSH / PR / BRANCH ACTION — OR ANY CLAIM OR QUESTION
ABOUT THEIR STATE — QUERY THE LIVE REMOTE. NEVER FROM MEMORY OR
CONVERSATION HISTORY. A PR MAY ALREADY BE MERGED OR CLOSED REMOTELY.
ASKING WHAT `gh pr view` ANSWERS IS A CHEAP QUESTION — CHECK, DON'T ASK.
Local branch view + conversation memory both go stale the moment anyone else — a maintainer, a parallel agent, an auto-merge rule — acts on the remote. Acting or asking on stale state is the recurring failure this kills (canonical: asking "shall I merge these 4 PRs?" when all four were already merged remotely). Run first, every time:
git fetch origin --quiet
gh pr view <number> --json number,state,mergeStateStatus,mergedAt,baseRefName
# state: OPEN | MERGED | CLOSED — act only on the live value
- A state question is self-answering — never ask "is it merged?",
"mergeable?", "did it push?", "still open?".
gh pr view/git fetchanswers it; asking is a cheap question (perno-cheap-questions). MERGED/CLOSED→ nothing to merge or push; report the live state and stop.- Before merging → re-fetch + re-read
state+mergeStateStatusin the same turn; never merge on a status seen earlier in the conversation. - "Based on main" / "current" → prove it with
git rev-list --count HEAD..origin/main(aftergit fetch); non-zero ⇒ behind and not current — mergemainin before opening a PR (see/create-pr§ 1b).
Conventions
→ See guideline docs/guidelines/php/git.md for branch naming, commit messages, PR conventions.
→ See commit-conventions rule for commit format, types, and scope rules.
→ Use conventional-commits-writing skill for generating/reviewing commit messages.
Procedure: Before opening a PR
- Quality pipeline + tests — only when
quality.local_auto_run: true(seequality-tools§ Execution policy): type-checker → auto-fixer → linter → type-checker, then the project's test command (detect from manifest:php artisan test/vendor/bin/phpunit(PHP),npm test/pnpm test/vitest/jest(JS-TS),pytest(Python),cargo test(Rust),go test ./...(Go)). Under the default (false/ missing) skip both — remote CI on the PR is the gate; say so instead of claiming they passed. - Rebase onto
main. - Fill in PR template completely.
Procedure: Finish a branch
When implementation is complete and all tests pass:
Work complete. What would you like to do?
1. Push and create a Pull Request
2. Keep the branch as-is (I'll handle it later)
3. Discard this work
Option 1: Push and create PR
- Run quality pipeline + tests (only when
quality.local_auto_run: true; defaultfalse→ skip, remote CI is the gate). git push -u origin <branch>.gh pr createusing PR template.
Option 2: Keep as-is
Report: "Branch <name> preserved locally." — do nothing.
Option 3: Discard
Confirm first — list branch name and commit count. Wait for explicit confirmation. Then:
git checkout main
git branch -D <feature-branch>
PR template
The project uses .github/pull_request_template.md:
- Jira ticket link (badge)
- Description — what and why
- Type of change
- Checklist (docs, rebase, quality, review, tests, QA)
- Links + screenshots
Default branch
mainis default/production branch.- Merge strategy: merge commits (not squash).
Procedure: Safe squash-after-push
Use ONLY when the user explicitly authorized a squash on a branch that is already on origin. The whole sequence runs in one turn — never end the session between rewrite and push.
Trigger context: git-history-discipline rule routed here.
1. Snapshot before touching anything
BRANCH=$(git branch --show-current)
DATE=$(date +%F)
git fetch origin
git tag "safe-squash-pre/${BRANCH}/${DATE}" HEAD
git tag "safe-squash-origin/${BRANCH}/${DATE}" "@{u}"
Two tags = two recoveries (local tip + origin tip). Do not skip the
tags — git reflog is TTL-bounded and unreliable across sessions.
2. Verify aligned starting state
git rev-list --left-right --count HEAD...@{u}
0 0→ aligned, proceed.N 0(local ahead) → unpushed work, proceed.0 N(origin ahead) →git pull --ff-onlyfirst, then re-check.M N(both non-zero) → divergent. Abandon the squash and run § Divergent-State Recovery below.
3. Perform the squash
Default — soft-reset path (single token-cheap rewrite):
git reset --soft "$(git merge-base HEAD <base>)"
git commit -m "<conventional commit message>"
Interactive rebase only when the user wants per-commit control — it
replays derived files (.condensation-hashes.json, router projections)
per commit and conflicts on every replay.
4. Re-push in the SAME turn
FETCHED_SHA=$(git rev-parse "@{u}")
git push --force-with-lease="${BRANCH}:${FETCHED_SHA}" origin "${BRANCH}"
git fetch origin
[ "$(git rev-parse HEAD)" = "$(git rev-parse @{u})" ] \
&& echo "OK: origin matches HEAD" \
|| echo "MISMATCH — do not end session"
If the push fails (pre-push hook, network, token budget):
- Fix the underlying cause now.
- Re-push immediately.
- Do not commit new work on top of the squashed-but-unpushed tip.
- Do not end the session until
HEAD == @{u}.
5. Hand off only with verified parity
Report exactly:
- pre-squash tip SHA (from step 1)
- pre-squash tag name (for recovery)
- post-squash tip SHA == origin SHA (verified in step 4)
- PR number, if any, and confirm it picked up the new tip
Procedure: Divergent-State Recovery
Fires when git rev-list --left-right --count HEAD...@{u} shows
both sides non-zero on the current branch.
1. Stop. Do not pull.
A blind git pull --rebase here replays remote commits on top of a
local history that may already represent the same work in a different
shape — guaranteed conflict storm in derived files, possible
double-application of the same change. This is the documented failure
mode behind git-history-discipline.
2. Tag both sides immediately
TS=$(date +%FT%H%M)
git tag "diverged-local/${TS}" HEAD
git tag "diverged-origin/${TS}" "@{u}"
3. Diagnose: which side is the correct future?
git log --oneline @{u}..HEAD # local-only commits
git log --oneline HEAD..@{u} # origin-only commits
git diff @{u}..HEAD --stat # shape of local-ahead work
Decision matrix:
| Pattern | Future | Action |
|---|---|---|
| Local has the same logical work as origin, just reshaped (squash/rebase) | Local | After PR-review check (step 4), git push --force-with-lease=<branch>:<origin-sha> |
| Origin has commits local does not reflect (another contributor pushed) | Origin | Tag any local-ahead work for cherry-pick, then git reset --hard @{u} |
| Both sides have genuine independent work | ask user | Never decide silently — surface the two commit lists and let the user pick |
4. PR review-comment check (mandatory before any force-push)
If a PR is open on this branch:
gh pr view --json reviews,comments
# or via GitHub API: /repos/<owner>/<repo>/pulls/<num>/{reviews,comments}
If review comments are anchored to commits that the force-push will erase → STOP, ask the user how to preserve them. A force-push that destroys live review feedback is unrecoverable from the agent side.
5. Recover or proceed
Use the tags from step 2 to restore either side if step 4 surfaces a
problem. After resolution, verify HEAD == @{u} and report both
SHAs plus the tags created.
Hard prohibitions on a pushed branch
- No
git pull --rebaseafter detecting divergent state. - No
git push --forcewithout--force-with-lease=<branch>:<sha>. - No squash-then-end-session — the push must complete in the same turn.
- No reflog-only recovery — always tag the state explicitly first.
Shared-branch & inherited commits — ask-before-drop protocol
Depth for the git-history-discipline
Iron Law on inherited & shared-branch commits (migrated per P4 of
road-to-kernel-and-router.md).
User often works in parallel with the agent, and multiple agents may share one
PR branch. A commit that looks "unrelated" or "stray" may be deliberate
in-flight work the user expects to keep. Reseating a branch onto a different
base, git reset --hard-ing away inherited commits, force-pushing over a
branch you did not create, or branching from a base with unexpected commits and
then "cleaning" them out all silently discard work — the exact failure that
law prevents.
Before ANY of these, STOP and ask (one numbered-options prompt per
user-interaction):
- reseating a branch's base (
git rebase --onto,git reset --hard <other-base>) in a way that drops commits already on the branch; - excluding / not-carrying-forward commits that were on the branch when you started this session;
- force-pushing (or
push <local>:<remote>-replacing) a branch carrying commits you did not author; - branching from a base with unexpected commits, then resetting them away.
Preserve-first is necessary but not sufficient. Even when you keep commits reachable (a save-branch / tag), you still ask before the branch the user sees loses them — "I preserved them locally" is not a substitute for the question; the user may be mid-edit on the shared branch and a force-push would clobber their in-flight work regardless of your local backup.
Two protective stops (for the protocol phase)
-
Pre-rewrite stop. Before any squash / amend / rebase on a branch on origin:
git fetch && git rev-list --left-right --count HEAD...@{u}. If either side is non-zero — STOP and run § Divergent-State Recovery. A blindgit pull --rebasein this state is the documented failure mode. (§ Safe squash-after-push steps 1–2 implement this stop.) -
Post-rewrite stop. After the rewrite, push in the same turn with
--force-with-lease=<branch>:<fetched-sha>and verifygit rev-parse origin/<branch>equalsgit rev-parse HEAD. If the push fails (hook, network, token budget) — fix the cause and re-push before ending the session, committing new work, or handing off. (§ Safe squash-after-push step 4 implements this stop.)
If either stop fires and resolution is not immediate → tag the state (git tag local-rewritten-tip-<ISO-date>) and hand control back to the user. Do not let a new session inherit a dirty divergence.
Equivalents that are also forbidden by default
git rebase -i(interactive)git rebase --autosquashgit commit --fixup/--squash(helpers that feed autosquash)git commit --amendon already-pushed commitsgit push --force/--force-with-lease(unless paired with the protocol)git reset --hardpast unpushed work the user might want- Squash-merge of a PR via API or CLI when the user has not picked the merge strategy
- Cherry-pick rewriting that drops or reorders commits
--amend on the current local commit before the first push is the narrow exception (treated as continuing to compose the commit, not rewriting history).
Amend-after-hook-failure trap (data-loss)
When a pre-commit hook fails, the commit did NOT happen — no new commit
object was created. A reflexive git commit --amend at that point does not
"retry the commit"; it rewrites the previous, already-good commit,
destroying that work. This is the one place the narrow --amend exception
above turns into data loss.
Recovery — never amend after a hook failure:
- Read the hook output and fix the cause (the lint/test/format failure).
- Re-stage the fix (
git add). - Create a NEW commit (
git commit, not--amend) — the prior commit was never overwritten and must stay intact.
(Migrated from git-history-discipline — recovery now lives next to the
mechanism it protects.)
Why history discipline exists
Interactive rebase + fixup loops generate disproportionate token cost every iteration: re-running CI per replayed commit, resolving the same content conflict in three derived files (.condensation-hashes.json, dist/router.json, .windsurfrules), losing the working tree to a stash that silently re-introduces older state. A single conflict can burn the budget of an entire feature.
A previous session squashed a pushed branch, the push hook failed at the token boundary, the session ended — and the next session saw local and origin pointing at different SHAs for the same logical work. A blind git pull --rebase cascaded into conflicts across every derived file. Recovery required forensic SHA-archaeology. The pre/post-rewrite stops make that sequence structurally impossible.
When you'd be tempted
- "I want commit 3 to come before commit 2 because the topic flows better." → don't. Reviewers read the PR diff.
- "There are two
chore: regeneratecommits, ugly." → don't. They are honest checkpoints. - "A linter caught an issue in commit 2 — let me fold the fix in." → don't. Add
fix(scope): …on top. - "I want to drop the WIP commit before pushing." → ask the user first.
- "Squash-merge when I open the PR will clean it anyway." → also true, also irrelevant — let the merge strategy do that work, not you.
- "My branch inherited some unrelated commits — I'll reseat it on
origin/mainso my PR is clean." → don't, ask first. May be the user's parallel work or another agent's. Preserve them and ask which base the user wants. - "The remote branch has commits I didn't author and no PR — I'll just force-push over it." → don't. No-PR is not no-owner; ask before replacing a branch you did not create.
Output format
- Commits following conventional commit format
- PR description with structured sections (if creating PR)
Gotcha
- Never commit/push/merge without explicit user permission.
- Keep subject line under 72 chars.
- Don't rebase shared branches.
git stashcan lose work — prefer WIP commits.
Do NOT
- Do NOT commit directly to
main. - Do NOT push without running quality tools first.
- Do NOT force-push to shared branches.
Auto-trigger keywords
- Git workflow
- branch naming
- commit message
- PR convention