Commit Workflow
Browser access. Use the built-in browser tools.
mcp__Claude_Browser__*covers navigation, DOM reads (read_page), screenshots andresize_window; reach for chrome-devtoolsemulatewhen a mobile device gate has to fire, whichresize_windowalone does not guarantee. Thebrowserskill and theagent-browsersteps were dropped in 8.79.0 — do not reach for that CLI here. (The binary itself is still installed for kb-factory's JS-rendered crawls; that is a separate consumer, not a fallback for page verification.)
Working Tree
!git status --short 2>/dev/null
!git diff --stat HEAD 2>/dev/null | tail -5
!git log --oneline -5 2>/dev/null
Quick Commit
# 1. Check what changed
git status --short
git diff --stat
# 2. Stage specific files (prefer targeted adds over git add -A)
git add src/components/new-feature.tsx src/lib/utils.ts
# 3. Commit with conventional format
git commit -m "feat: add playlist drag-drop reorder"
Evidence goes IN the commit
If the change fixes a defect or moves a visible surface, the prove skill's
before/after pair belongs in this commit, not beside it. Two reasons, and the
second is the one that bites:
- A reviewer reading the commit later has no other route to it.
.claude/evidence/is tracked, so evidence left uncommitted dirties the tree and any gate refusing a dirty tree then refuses to run at all.
Name the paths in the body and state the delta in one line. A difference you cannot state in a sentence is one you have not checked.
Conventional Commits (Required)
<type>: <short description>
[optional body]
| Type | When |
|---|---|
feat |
New feature |
fix |
Bug fix |
refactor |
Code restructure, no behavior change |
chore |
Dependencies, config, tooling |
docs |
Documentation only |
test |
Add or update tests |
perf |
Performance improvement |
Rules:
- Subject line < 70 chars
- Imperative mood: "add" not "added"
- No period at end
- Body explains WHY, not WHAT
- Include story ID when available:
feat(S13-001): add playlist UI
Commit, then ask before pushing
git add <files>
git commit -m "feat: description"
Stop at the commit. rule-local-first/SKILL.md holds that an ad-hoc
git push, PR or merge needs the operator to say so in that turn, and that "it is
ready to push" is a status line rather than a licence. Report the commit and ask.
Once the answer is yes, in that turn:
git push origin HEAD
When a git hook refuses the commit or the push
A commit-msg or pre-push hook that exits non-zero is a gate, and
--no-verify skips it. The PreToolUse guard asks before that flag runs, and
its reason names the hook file and the script it runs. The question that decides
whether skipping is right is not is the gate red but is it red at the base
branch too, and it costs one detached worktree to answer:
git fetch -q origin
BASE=$(mktemp -d)
git worktree add -q --detach "$BASE" origin/main # or the default branch
( cd "$BASE" && node <the script the hook runs> ); echo "base exit $?"
node <the same script>; echo "branch exit $?"
git worktree remove --force "$BASE"
Compare the FAIL lines, not the exit codes: two reds with different lines are two different findings.
| base | branch | verdict |
|---|---|---|
| green | red | the red is this change's; fix it, no bypass |
| red | red, same lines | a trunk red; the bypass is correct and the RECORD is the deliverable |
| red | red, more lines | both; fix the extra lines, then the rest is trunk's |
Then, with the operator's yes in that turn, push with --no-verify and put one
line in the commit or PR body naming the gate skipped, why it was red, and that
it reproduces at the base. The PostToolUse note after the push asks for exactly
this line. [measured 2026-09-07] four sessions bypassed a trunk-red pre-push in
one night and every one was right; the bypass that could be told from a skipped
gate afterwards was the one that wrote the line down.
Branch Strategy
Check before branching:
# Solo project? (1 contributor, no branch protection)
CONTRIBUTORS=$(git shortlog -sn --all 2>/dev/null | wc -l)
HAS_REMOTE=$(git remote 2>/dev/null | head -1)
- Solo project (1 contributor or no remote): commit directly to main — branching adds ceremony with zero value.
- Team project (2+ contributors or CI/branch protection): create a feature branch from main.
# Only branch for team projects
if [ "$CONTRIBUTORS" -gt 1 ] && [ -n "$HAS_REMOTE" ]; then
BRANCH=$(git branch --show-current)
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
git checkout -b feat/[descriptive-name]
fi
fi
Post-Commit Quick Check
After every commit, run a 5-second sanity check:
npm run build 2>&1 | tail -3
# If dev server running, check for console errors
curl -s http://localhost:3000 > /dev/null 2>&1 && echo "server up - verify with navigate + read_page"
If errors found, fix immediately and amend the commit.
Full PR Flow (commit-push-pr)
# 1. Create branch if on main
BRANCH=$(git branch --show-current)
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
git checkout -b feat/[descriptive-name]
fi
# 2. Stage and commit
git add <files>
git commit -m "feat: add playlist UI with drag-drop"
# 3. Push -- ONLY once the operator has said so in this turn
git push -u origin HEAD
Auto-Generate PR Description from prd.json
If prd.json exists and has completed stories, generate the PR body from them:
node -e "
const p=require('./prd.json');
const stories=p.stories||{};
const done=Object.entries(stories).filter(([,s])=>s.passes===true);
if(done.length){
console.log('## Changes');
done.forEach(([id,s])=>console.log('- **'+id+'**: '+s.title+(s.resolution?' ('+s.resolution+')':'')));
console.log('\n## Test Plan');
done.forEach(([id,s])=>console.log('- [ ] Verify '+s.title));
}
"
Use this output as the PR body:
gh pr create --title "[Sprint summary]" --body "[generated from prd.json]"
Safety Checks
Before committing (if ANY fail, fix before proceeding):
-
npm run typecheckpasses -
npm run buildpasses -
npm test -- --watchAll=false --passWithNoTestspasses - No
.envfiles staged — unstage if found - No
console.login staged files — remove if found - No hardcoded secrets — remove if found
Before pushing:
- Branch is correct (not pushing to main accidentally)
- Branch is up-to-date with remote:
git fetch && git status - Commit messages are clean
If issues found: fix them, re-stage, re-run checks, THEN commit.
Version Sync Check (claude-auto-dev repo only)
When committing to this repo, check for stale version strings before staging:
# Read current version
VERSION=$(cat VERSION 2>/dev/null)
# Grep for previous version references (skip CHANGELOG.md - it's historical)
grep -rn "4\.9\.4\|v4\.9" --include="*.md" --include="*.json" --include="*.ps1" --include="*.sh" . \
| grep -v CHANGELOG.md | grep -v node_modules | grep -v .git
If stale versions found: fix them before committing.
In a repo that ships Claude Code plugins, the version lives in VERSION,
package.json, .claude-plugin/marketplace.json, and every
plugins/*/.claude-plugin/plugin.json. Do not edit them by hand — run the
repo's bumper (node tooling/bump.js <x.y.z> in this project) so they cannot
drift, then update the README badge.
The current version is: !cat VERSION 2>/dev/null
Batch Commit (During Auto Mode)
During auto, commit every 3 tasks:
git add -A -- ':!.env*' ':!*.pem' ':!*.key' ':!*.secret'
git commit -m "feat: complete S9-1 through S9-3
- S9-1: Playlist UI with drag-drop
- S9-2: Song extend from timestamp
- S9-3: Onboarding wizard"
Amend Last Commit
Only if not pushed yet:
git add <missed-files>
git commit --amend --no-edit
Undo Last Commit (Keep Changes)
git reset --soft HEAD~1