Git workflow
Skill event4u-app/agent-config/dist/agent-src/skills/git-workflow
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.From its SKILL.md
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.
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
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.