From git-workflow
Decide commit readiness from real repository state, draft Conventional Commit messages, and publish GitHub pull request or GitLab merge request bodies that preserve repository templates. Use this skill when the user asks to decide whether changes are ready for one commit, draft a Conventional Commit message from the real diff, prepare a pull request or merge request body, or preserve GitHub/GitLab repository templates while publishing a change.
npx claudepluginhub ririnto/sinon --plugin git-workflowThis skill uses the workspace's default tool permissions.
Use this skill to turn real repository state into a publishable change narrative. The common case is inspecting the working tree, deciding whether the changes are cohesive enough for one commit, drafting a Conventional Commit message from the actual diff, preparing hosted review text, and then branching into GitHub or GitLab-specific template depth only when the repository host makes that neces...
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Use this skill to turn real repository state into a publishable change narrative. The common case is inspecting the working tree, deciding whether the changes are cohesive enough for one commit, drafting a Conventional Commit message from the actual diff, preparing hosted review text, and then branching into GitHub or GitLab-specific template depth only when the repository host makes that necessary.
git status, staged changes, unstaged changes, and recent commit history before proposing commit or review text.git diff --cached, not from the full working tree.auth, api, or docs; omit it when it adds noise.SKILL.md.| Situation | Autonomous decision | Must ask user |
|---|---|---|
| Choosing commit type from diff content | Yes | No |
| Choosing scope from file paths | Yes (if clear); No (if ambiguous) | Yes (if ambiguous) |
| Split vs. one-commit decision | Propose + flag | Yes (final call is user's) |
| Subject wording | Draft | Yes (user approves final wording) |
| Which named template to use | Detect from context | Yes (if ambiguous or multiple exist) |
| Whether to proceed with commit | No | Always ask before executing |
| Validation claims | Report actual state | No |
Phrasing templates for questions to the user:
git diff --cached shows [summary] but git diff shows additional [summary]. Draft commit from staged changes only?"Rule: NEVER execute git commit autonomously; always present the draft and wait for user confirmation.
Inspect the repository state before choosing wording:
git status
git diff --cached
git diff
git log -5 --oneline
git status output signals:
| Output pattern | Meaning | Next action |
|---|---|---|
nothing to commit, working tree clean | No changes at all | Stop; nothing to publish |
Changes not staged for commit: only | Unstaged changes exist | Recommend staging first; any draft is provisional |
Changes to be committed: only | Clean staging area | Proceed with commit drafting |
| Both sections present | Staged/unstaged mismatch | Draft from --cached only; call out unstaged portion |
Untracked files: present | New files not yet tracked | Include in staging recommendation if relevant |
git diff --cached output signals:
| Output pattern | Meaning | Next action |
|---|---|---|
| Empty (no output) | No staged changes | Do not present final commit as ready-to-run |
| Shows file list with stats | Staged changes exist | Use as sole source for commit message |
Contains Binary files differ | Binary files changed | Note in summary; do not describe binary content |
git diff output signals:
| Output pattern | Meaning | Next action |
|---|---|---|
Same files as --cached | Working tree matches staging | Normal single-commit path |
| Different or additional files | Unstaged drift exists | Exclude from commit draft; flag to user |
git log -5 --oneline output signals:
| Pattern | Meaning | Usage |
|---|---|---|
Consistent type/scope pattern (e.g., feat(auth): ...) | Repository has established convention | Match the established style |
| Mixed conventions or no pattern | No established style | Apply Conventional Commits cleanly; do not invent repo-specific patterns |
| Empty (no commits yet) | Initial repository | Skip history check; base type/scope on files alone |
Use when: you need to anchor commit or hosted-review text to the real repository state before writing anything.
Detect the repository host before branching into host-specific references:
git remote -v
ls -d .github 2>/dev/null && echo "HAS_GITHUB_DIR"
ls -d .gitlab 2>/dev/null && echo "HAS_GITLAB_DIR"
| Signal | Host determination | Action |
|---|---|---|
Remote URL contains github.com | GitHub | Open ./references/github-pull-request-templates.md for template mechanics |
Remote URL contains gitlab.com or self-hosted GitLab domain | GitLab | Open ./references/gitlab-merge-request-templates.md for template mechanics |
.github/ directory exists but no GitHub remote | Likely GitHub | Treat as GitHub for template purposes; note remote not confirmed |
.gitlab/ directory exists but no GitLab remote | Likely GitLab | Treat as GitLab for template purposes; note remote not confirmed |
| Neither signal detected | Unknown | Stay host-neutral; use fallback review body from this file; do not open either reference |
Example detection outcomes:
# GitHub repository
origin https://github.com/org/repo.git (fetch)
-> Host: GitHub
# Self-hosted GitLab
origin https://gitlab.example.com/group/project.git (fetch)
-> Host: GitLab (self-hosted)
# Local-only repository, but .github/ directory exists
(no remotes)
HAS_GITHUB_DIR
-> Host: likely GitHub (template presence is strong signal)
# No signals at all
(no remotes, no .github/, no .gitlab/)
-> Host: unknown — stay neutral
Rule: when host is unknown, use the fallback review body from SKILL.md and do not open either reference.
If git diff --cached is empty, do not present a final commit message as ready-to-run. Say that no staged diff exists yet, and keep any wording provisional until the intended changes are staged.
If git diff --cached and git diff show different concerns, write the commit message from the staged diff only and note that the unstaged portion is out of scope for this commit.
Single logical change ready for one commit:
git status
git diff --cached
git commit -m "fix(auth): reject expired refresh tokens" -m "- Prevent expired refresh tokens from creating new sessions.
- Keep token rejection behavior aligned with the API contract."
<type>[optional scope]: <description> # description is REQUIRED
- Explain the user-visible or maintenance reason for the change.
- Note the main constraint, compatibility point, or safety boundary.
A scope MUST be a noun describing a section of the codebase, contained within parentheses (e.g., fix(parser):).
The type MUST be a noun. Use feat, fix, docs, style, test, refactor, perf, build, ci, chore, or revert, and prefer feat or fix whenever published behavior changes. Use revert only with the dedicated revert format documented below; it is not interchangeable with the other types. If the diff mixes unrelated feature work, broad cleanup, or separate test concerns, split the commit before drafting final text.
Quick type choices:
| If the real change primarily... | Prefer... | Example |
|---|---|---|
| adds or expands user-visible behavior | feat | feat(search): add prefix matching for tags |
| corrects incorrect behavior | fix | fix(auth): reject expired refresh tokens |
| changes documentation only | docs | docs(api): clarify token rotation flow |
| changes formatting, whitespace, or style without changing logic | style | style(auth): normalize indentation in login handler |
| adds or corrects tests without changing runtime behavior | test | test(auth): cover expired token rejection |
| restructures code without changing behavior | refactor | refactor(cache): extract key builder |
| improves performance characteristics | perf | perf(query): reduce duplicate index scans |
| changes tooling, packaging, or automation | build, ci, or chore | ci(actions): cache pnpm store |
| reverts a prior commit (use the dedicated revert format below) | revert | revert: reject expired refresh tokens |
A scope MUST be a noun describing a section of the codebase, contained within parentheses (e.g., fix(parser):).
Add a scope only when it clarifies the affected surface. Derive from the changed file paths, not from guesses.
| Changed paths match... | Suggested scope | Example |
|---|---|---|
src/**/auth/**, src/auth/**, **/auth* | auth | fix(auth): reject expired tokens |
src/**/api/**, controllers/**, routes/**, handlers/** | api | feat(api): add user endpoint |
docs/**, *.md (non-skill docs), README* | docs | docs(api): clarify token flow |
**/*Test*, **/test/**, __tests__/**, **/*_test.*, **/*.spec.* | test (or module scope) | test(auth): cover rejection |
**/package.json, **/pom.xml, **/build.gradle*, **/Cargo.toml | build | chore(build): bump dependency |
.github/workflows/**, .gitlab-ci.yml, Jenkinsfile*, **/Makefile | ci | ci(actions): cache store |
**/config/**, *.env.example, *.conf | config | chore(config): add rate limit |
Multi-directory rules:
Broken-vs-correct scope choices:
# Broken — scope adds noise without clarification
chore(src): update dependencies
# Correct — scope clarifies the affected surface
chore(build): bump lodash from 4.x to 5.x
# Broken — over-specified for cross-module refactor
refactor(core, api, auth, db): extract shared validation
# Correct — omits scope when too many modules touched
refactor: extract shared validation into common package
Subject line rules:
BREAKING CHANGE).<type>(<scope>): <summary> or <type>: <summary>.Body rules:
- ).Complete well-formed example:
feat(search): add prefix matching for tags
- Allow tag queries to match on prefix when exact match returns
no results.
- Preserve case-insensitive comparison per the search contract.
Refs: PROJ-123
Formatting anti-patterns:
# Broken — trailing period, past tense, no blank line before body
fix(Auth): Rejected expired tokens.
Token rejection was updated.
# Correct — imperative, no period, blank line before body
fix(auth): reject expired refresh tokens
- Prevent expired refresh tokens from creating new sessions.
Standard footer format: <token><separator><value>. Footers go after a blank line following the body. Multiple footers are separated by blank lines.
Footer format rules:
<token><separator><value>.: (colon + space) or # (space + hash).- instead of spaces (e.g., Acked-by, Reviewed-by).BREAKING CHANGE MAY contain a space.| Footer token | When to use | Example |
|---|---|---|
Refs | Issue/tracker reference | Refs: PROJ-123 or Refs #123 |
Closes | Issue this commit resolves | Closes: #42 |
BREAKING CHANGE: or BREAKING-CHANGE: | API/behavior breaking change | BREAKING CHANGE: removes legacy endpoint |
Co-authored-by | Pair programming or co-author credit | Co-authored-by: Name <email> |
Reviewed-by | Code review credit | Reviewed-by: Alice <alice@example.com> |
Acked-by | Acknowledgement credit | Acked-by: Bob <bob@example.com> |
BREAKING CHANGE detail: write a full paragraph explaining the migration path, not just a label.
Revert format:
revert: <description of why the revert happened>
Refs: <sha1>, <sha2>
For multiple reverted commits, list all SHAs in the footer.
Breaking changes MAY appear on any type, not just feat:
fix(auth)!: change default token expiry semanticschore(deps)!: upgrade to v5 with removed APIsdocs!: restructure all URL paths in documentationBreaking change variant with explicit footer (recommended for complex migrations):
feat(api)!: remove legacy session refresh endpoint
- Replace the legacy refresh endpoint with the token-rotation flow.
- Keep server behavior aligned with the new authentication contract.
BREAKING CHANGE: Clients must switch from `/v1/session/refresh`
to `/v2/tokens/refresh`. The legacy endpoint returns 404 after
this release.
Breaking change variant with ! only (no footer needed; the description IS the breaking change notice):
feat!: drop support for Node 6
Split instead of forcing one commit when any of these signals fire:
Signal 1: Subject would contain "and"
# Diff touches auth/login.js (bug fix) and utils/format.js (cosmetic cleanup)
# Forced single-commit subject:
fix(auth): reject expired tokens and format date strings
# ^^^^ mixed purpose -> SPLIT
# Correct split:
fix(auth): reject expired tokens
style: normalize date string formatting in utils
Signal 2: Independent revert boundary
# Diff adds a feature flag AND removes dead code
# If you revert the commit, you lose the feature flag AND restore dead code
# These have independent rollback semantics -> SPLIT
# Correct split:
feat(flags): add dark-mode toggle
refactor: remove unused legacy theme helpers
Signal 3: Different validation story
# Diff fixes a bug (needs regression test) AND refactors naming (needs lint only)
# Validation requirements differ per concern -> SPLIT
# Correct split:
fix(db): handle null foreign key in user query
refactor(models): rename userId to owner_id across ORM layer
Split decision procedure:
Split proposal format to present to the user:
Recommend splitting into N commits:
1. <type>(<scope>): <subject>
- Files: <list>
- Why: <reason>
2. <type>(<scope>): <subject>
- Files: <list>
- Why: <reason>
Proceed as one commit or split?
Translate raw diff content into each section of the hosted review body.
Extract WHAT changed from the diff. Rules:
# Raw diff: auth/login.js (+15/-3), auth/session.js (+8/-2), auth/middleware.js (+5/-0)
# Good Summary:
- Fix token validation in auth module to reject expired sessions
- Add session cleanup after failed authentication attempts
# Bad Summary (copies diff):
- Modified auth/login.js, auth/session.js, auth/middleware.js
Extract WHY from the nature of the change. Rules:
Map verification methods to what actually changed:
| What changed | Suggest validation | Rule |
|---|---|---|
| Runtime code | Unit tests, integration tests, manual smoke test | List ONLY checks that were run or can be run now |
| Configuration | Config validation, smoke test, dry-run | Do not list checks that don't apply |
| Documentation | Link check, preview render, accuracy review | Be specific about what was checked |
| CI/CD config | Pipeline dry-run, lint, action syntax check | Name the specific pipeline or workflow |
| Tests only | Test suite pass, coverage check | Note if coverage threshold met |
Rule: list ONLY checks that were actually performed or can be performed now. Never fabricate validation entries.
Identify risks from the diff content:
| Risk signal in diff | Risk text | When to write None |
|---|---|---|
| API signature change, removed parameter | Backward compatibility: callers must update | No breaking changes, no deployment ordering concern |
| Database migration, schema change | Migration required; deploy order matters | No schema or data changes |
| Dependency version bump | Transitive dependency conflicts possible | No dependency changes |
| Performance-critical path modified | Regression risk under load | Changes are purely additive or cosmetic |
| Auth/security touched | Review access control impact | No auth/security surface changed |
If no identifiable risk exists, write None.
Hosted review body shape to adapt when a repository template exists but the exact body must still be filled with real change details:
## Summary
- <fill with the actual change>
## Why
- <fill with the actual motivation>
## Validation
- <fill with tests, checks, or manual verification actually performed>
Fallback hosted review body when no repository template exists:
## Summary
- <1-3 bullets describing the real diff>
## Why
- <reason the change exists>
## Validation
- <tests, lint, typecheck, or manual checks that actually ran>
## Risks
- <follow-up risks, rollout notes, or `None`>
| If the blocker is... | Read... |
|---|---|
| exact GitHub pull request template paths, named template handling, or GitHub-specific preservation rules | github-pull-request-templates.md |
exact GitLab merge request template paths, Default.md, variables, or quick-action preservation rules | gitlab-merge-request-templates.md |
Complete scenario from dirty working tree to final output.
Starting state: a repository with mixed staged and unstaged changes in the auth module.
Step 1: Inspect repository state
$ git status
On branch feature/auth-token-cleanup
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/auth/login.ts
modified: src/auth/session.ts
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/auth/middleware.ts
$ git diff --cached
diff --git a/src/auth/login.ts b/src/auth/login.ts
index abc1234..def5678 100644
--- a/src/auth/login.ts
+++ b/src/auth/login.ts
@@ -45,7 +45,10 @@ export async function login(credentials: Credentials): Promise<Session> {
const user = await findUserByEmail(credentials.email);
+ if (!user) {
+ throw new AuthenticationError('invalid credentials');
+ }
const token = generateToken(user);
const session = await createSession(user.id, token);
return { user, token, session };
}
diff --git a/src/auth/session.ts b/src/auth/session.ts
index def5678..ghi9012 100644
--- a/src/auth/session.ts
+++ b/src/auth/session.ts
@@ -12,6 +12,9 @@ export async function createSession(userId: string, token: string): Promise<Session> {
const expiresAt = new Date(Date.now() + SESSION_TTL);
+ if (isExpired(token)) {
+ throw new SessionError('token expired');
+ }
return db.sessions.insert({ userId, token, expiresAt });
}
$ git diff
diff --git a/src/auth/middleware.ts b/src/auth/middleware.ts
--- a/src/auth/middleware.ts
+++ b/src/auth/middleware.ts
@@ -8,7 +8,7 @@ export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
- const session = req.headers['x-session-id'];
+ const session = req.headers['x-session-id'] ?? req.cookies?.session_id;
if (!session) {
return res.status(401).json({ error: 'unauthenticated' });
}
next();
}
$ git log -5 --oneline
a1b2c3d (HEAD -> feature/auth-token-cleanup) feat(auth): add OAuth2 provider support
e4f5g6h fix(auth): handle missing user avatar gracefully
i7j8k9l feat(api): add user profile endpoint
m0n1o2p chore(ci): upgrade Node.js to 20 in CI workflow
q3r4s5t docs(readme): update installation instructions
Step 2: Decide publication approach
login.ts and session.ts (both auth module, same purpose: input validation).middleware.ts (cookie fallback, different concern).Step 3: Choose type and scope
fix (corrects incorrect behavior: missing validation allowed invalid/expired input).auth (all changed files under src/auth/).Step 4: Draft commit message
fix(auth): validate credentials and token before session creation
- Reject login early when email does not match any user.
- Throw explicit error for expired tokens during session creation.
Step 5: Detect host
$ git remote -v
origin https://github.com/example/webapp.git (fetch)
-> Host: GitHub
Step 6: Prepare review body (using fallback template)
## Summary
- Fix auth module to validate user existence and token freshness before creating sessions
## Why
- Unvalidated login allowed null-user token generation; expired tokens created orphaned sessions
## Validation
- Added unit tests for null-email rejection in login.ts
- Added unit tests for expired-token rejection in session.ts
- Manual: verified 401 response on expired token replay
## Risks
- Clients sending expired tokens receive 401 instead of 500; verify error handling upstream
Step 7: Validate result
fix is valid; scope auth matches file paths.git diff --cached only.Step 8: Output contract fulfillment
fix(auth): validate credentials and token before session creation (ready-to-run).| Edge case | Detection | Response |
|---|---|---|
| Empty repository (no commits) | git log returns empty | Skip history check; base type/scope on files alone; note this is initial commit |
| First commit on new branch | git log --oneline shows 0 entries for branch | Normal flow; note branch is new |
| Detached HEAD | git symbolic-ref HEAD fails | Warn user; recommend creating or checking out a branch first; do not draft commit for detached HEAD |
| Binary files in diff | git diff --cached shows Binary files differ | Note binary files in summary by name; do not attempt to describe binary content |
| Large diff (>200 lines) | git diff --cached | wc -l > 200 | Summarize by file rather than listing every hunk; consider split recommendation |
| Submodules present | .gitmodules exists or git submodule status shows entries | Note submodules in validation; do not include submodule state in commit message unless explicitly changed |
| Only untracked files | git status shows untracked but no staged/unstaged modifications | Recommend staging relevant files first; do not draft commit from untracked-only state |
| Merge conflict markers | grep -r '^<<<<<<< ' . finds matches | Stop; tell user to resolve conflicts before drafting commit |
| Stash present | git stash list is non-empty | Note stash existence but do not pop or drop; proceed with working tree as-is |
| Signed commits required | git config --get commit.gpgsign returns true | Note GPG signing will be applied; do not attempt to sign or bypass |
.gitignore issues | Changed files appear in git status that should be ignored | Flag suspicious tracked files; recommend checking .gitignore before committing |
Return:
Validate the result with these executable checks:
# 1. Staged changes exist
echo "=== Staged changes ==="
git diff --cached --stat
# Must show at least one file; if empty, result is blocked
# 2. Unstaged drift awareness
echo "=== Unstaged changes (excluded from commit) ==="
git diff --stat
# Compare to staged stat; flag mismatches to user
# 3. Subject length check
echo "=== Subject length (must be <= 72) ==="
echo -n "<proposed subject>" | wc -c
# 4. Type validity
echo "=== Type check ==="
echo "<proposed type>" | grep -E '^(feat|fix|docs|style|test|refactor|perf|build|ci|chore|revert)$'
# Must exit 0
# 5. Recent history consistency
echo "=== Recent history (style consistency check) ==="
git log -5 --oneline
# 6. Template preservation (when host is known)
# Re-run discovery command from Host Detection or host-specific reference
# Confirm sections match the discovered template
Pass/fail criteria:
| Check | Pass condition | Fail action |
|---|---|---|
| Staged changes exist | --cached --stat shows files | Block: tell user to stage files |
| Subject length | <= 72 characters | Rewrite subject to fit |
| Type validity | Matches allowed types list | Change to valid type |
| History consistency | Proposed style does not conflict with established repo pattern | Adjust to match repo or justify deviation |
| Template preservation | Filled sections are subset of template sections | Re-fill using correct template structure |
| Validation honesty | Only actually-run checks listed | Remove any planned-but-not-run checks |
| Anti-pattern | Why it fails | Correct move |
|---|---|---|
| mixing unrelated work into one commit | review and rollback become harder | split the changes into separate logical commits |
writing subjects like update files | the commit history loses meaning | write an imperative summary tied to the actual change |
| copying the diff into hosted review text | reviewers lose the higher-level story | summarize the change, motivation, and validation instead |
| moving the common path into host-specific references | the main skill stops being self-sufficient | keep commit drafting and fallback review guidance in SKILL.md |
| checking boxes for work that has not been done | the review narrative becomes misleading | mark only the items that are actually true |
| drafting commit from unstaged diff | the committed content will not match the message | always draft from git diff --cached |
| omitting scope when files cluster in one module | commit loses navigational signal | add scope when changed files share a clear module boundary |
| writing body that restates file names | body adds no value beyond the diff | explain WHY the change exists, not WHICH files changed |