# Qc

> Quick commit + auto-push. Stages changes, drafts a Conventional Commit message from the diff, commits, and pushes. `/qc` pushes the current branch to its origin upstream; `/qc main` switches to main first and pushes there (solo-speed flow — skips PR review). Bootstraps `gh` (install + browser auth) if not already configured. Never bypasses hooks (`--no-verify`), signing (`--no-gpg-sign`), or rewrites commits (`--amend`). No AI attribution lines in commit messages.

- Skill: `timothyvang/qc` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add timothyvang/qc`
- Raw SKILL.md: https://api.skillmd.com/api/skills/timothyvang/qc/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: timothyvang (https://skillmd.com/u/timothyvang)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/timothyvang/qc

---


# qc — quick commit + auto-push

Portable, project-agnostic quick-commit skill. Drop this file at `~/.claude/skills/qc/SKILL.md` to make `/qc` available in every project on the machine.

A project may override this with its own `.claude/skills/qc/SKILL.md` (project-local takes precedence). Use that for project-specific commit conventions (e.g. required task IDs).

## Two invocations

- **`/qc`** — commit + push the **current branch** to its upstream on origin. Default. Use during normal feature-branch work.
- **`/qc main`** — switch to `main` (or `master`, whichever the repo's default branch is — detect with `git symbolic-ref refs/remotes/origin/HEAD`), commit, push origin's default branch directly. **Solo-speed flow** — skips the PR review path. Only use when the user is the only reviewer and the change is ready.

If `/qc main` is invoked while on a feature branch with uncommitted changes, carry the changes across with `git stash --include-untracked` → `git checkout <default>` → `git stash pop` → resolve any conflicts → continue. If conflicts can't be resolved cleanly, **stop** and surface to the user; do not throw away work.

## Step 0 — Prerequisite: `gh` installed and authenticated

`/qc` relies on `gh` having configured git's credential helper so `git push` over HTTPS doesn't prompt. Before staging anything, verify:

```bash
command -v gh >/dev/null && gh auth status >/dev/null 2>&1
```

If that exits non-zero, bootstrap **before** continuing.

### Install `gh` if missing

Detect platform first; pick the matching command:

- **Ubuntu / Debian:**
  ```bash
  type -p curl >/dev/null || sudo apt install -y curl
  curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
    | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
  sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
  echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
    | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
  sudo apt update && sudo apt install -y gh
  ```
- **macOS (Homebrew):** `brew install gh`
- **Fedora / RHEL / CentOS Stream:** `sudo dnf install -y gh`
- **Arch:** `sudo pacman -S --noconfirm github-cli`
- **Alpine:** `sudo apk add github-cli`
- **Windows (winget):** `winget install --id GitHub.cli`
- **Other / unknown:** point the user at https://cli.github.com/ and stop.

`sudo` will prompt the user — that is expected; do not try to bypass.

### Authenticate via browser (one-time)

```bash
gh auth login --hostname github.com --git-protocol https --web
```

This prints a one-time code, opens the user's browser to `https://github.com/login/device`, and configures git's credential helper on success.

If the host is headless and can't open a browser:

```bash
gh auth login --hostname github.com --git-protocol https
```

Follow the prompts (paste-the-code flow works without a local browser). For SSH protocol instead, swap `--git-protocol https` → `--git-protocol ssh` and let `gh` upload an SSH key for the user.

### Verify

```bash
gh auth status         # expect "Logged in to github.com account <user>"
git push --dry-run     # confirms credential helper is active for this repo
```

Re-run the `gh auth status` check after install/auth before continuing to staging. If any step still fails, **stop** and surface the failure to the user — do not commit work that can't be pushed.

## Step 1 — Inspect the working tree

Run in parallel; read the actual changes before drafting any message:

```bash
git status
git diff --stat
git diff --cached
git log -5 --oneline
```

Look for:
- Files the user may not have meant to commit (`.env`, `*.key`, `node_modules/`, large binaries, `__pycache__/`).
- Two unrelated concerns mixed in one diff (split into two commits if so — see "Multi-commit batches" below).
- The repo's commit-message conventions, inferred from `git log` (Conventional Commits? Project-specific tags like `[JIRA-123]` or `[W1.A.0]`? Plain prose?). Match the existing style.

## Step 2 — Detect project conventions

Before drafting the message, check for project-specific rules in this order (stop at the first hit):

1. **`CLAUDE.md` at repo root** — if it specifies a commit format (e.g. Conventional Commits + task ID), follow it exactly.
2. **`CONTRIBUTING.md`** — same; many projects encode commit rules here.
3. **`.gitmessage` template or `commit.template` in `.git/config`** — pre-fill it.
4. **Recent commit log** — match the dominant style (`type(scope): summary` if Conventional, `JIRA-123: ...` if ticket-prefixed, plain imperative one-liner otherwise).

If a project mandates a task ID (e.g. `[W1.A.0]`, `JIRA-123`, `#42`), resolve it from:
- An explicit ID the user gave in this turn.
- `git log -20 --pretty=%s | grep -oE '<id-pattern>' | head -1` to reuse the active task.
- Project's plan file (`BUILD_PLAN.md`, `ROADMAP.md`, `.linear/`, etc.) cross-referenced with the touched paths.
- **If still unresolved, ask** before defaulting.

## Step 3 — Stage

Prefer named-path staging over `git add -A`:

```bash
git add path/to/file.py path/to/other.md
```

This avoids sweeping in `.env`, credentials, `__pycache__/`, build artifacts, or large binaries the user didn't mean to commit.

`git add -A` / `git add .` is acceptable **only** if:
- The user explicitly said "everything" / "all changes" / "stage all", AND
- `git status` shows no unexpected files (no `.env*`, no `*secret*`, no large unfamiliar additions).

## Step 4 — Draft the commit message

One sentence, **imperative voice** ("add", "fix", "remove" — not "added", "adding"), focused on **why** the change is being made, not what files changed (the diff already shows that).

Defaults if no project convention is detected:
- Format: `<type>(<scope>): <summary>`
- `type` ∈ {`feat`, `fix`, `test`, `chore`, `docs`, `refactor`, `perf`, `style`, `ci`, `build`, `revert`}.
- `scope` is optional but recommended — the module/area touched.
- Summary ≤ 70 chars where possible.
- Body only when the change is non-obvious; skip otherwise.

Verb mapping when picking `type`:
- New user-visible capability → `feat`
- Behavior bug fix → `fix`
- Test added/changed without behavior change → `test`
- Version pins, tooling, config, dependencies → `chore`
- Docs only → `docs`
- No-behavior-change cleanup → `refactor`
- Performance only, no behavior change → `perf`

**Forbidden in commit messages:**
- `Co-Authored-By: Claude …`, `🤖 Generated with [Claude Code]`, or any AI-attribution line. Authorship is git committer + signature only.
- Padding like `Update files.` or `Misc changes.` — say what changed and why.

State the drafted message to the user in one line before committing, so they can correct it if needed.

## Step 5 — Commit

```bash
git commit -m "<type>(<scope>): <summary>"
```

**Hard rules — never violate:**
- No `--no-verify` (skipping pre-commit / commit-msg hooks).
- No `--no-gpg-sign` (skipping GPG signing if the repo requires it).
- No `git commit --amend` to fold in a fix — that rewrites history. **Always make a new commit.**
- No `--allow-empty` unless explicitly requested.

**If a pre-commit hook fails:** read the failure output, fix the underlying issue (lint, format, type-check, etc.), re-stage the fix, and create a **new** commit (the hook failure means the prior commit didn't happen — `--amend` would fold into the *previous* successful commit, which is wrong). If the hook fails repeatedly on the same issue, **stop** and root-cause; do not loop.

**If the user asks you to bypass hooks**, refuse and explain why. Hooks exist because the project's maintainers wanted them; bypassing is a project-policy decision, not a one-off override. Ask the user to either fix the issue or, if they're sure, run the bypass themselves with `git commit --no-verify` — don't do it on their behalf.

## Step 6 — Push

```bash
git push -u origin HEAD
```

If the branch already tracks an upstream, plain `git push` is fine.

For `/qc main`:

```bash
git push origin <default-branch>
```

**Force-push is not part of `/qc`.** If `git push` rejects with non-fast-forward, **stop** and surface to the user. Do not `--force` or `--force-with-lease` autonomously — that overwrites someone else's work and is destructive.

## Multi-commit batches

If the staged changes are obviously two unrelated concerns (e.g. a feat + an unrelated docs fix), **split into two commits with two messages** rather than mushing them together. State the split plan before staging the first one.

A reasonable rule of thumb: if you'd write two different `type(scope)` headers for the same diff, it's two commits.

## What `/qc` does *not* do

- Open PRs (`gh pr create --fill`) — that's a separate `/ship` or `gh pr create` step.
- Merge PRs (`gh pr merge`) — same.
- Run tests / lint / type-check manually — pre-commit hooks own that. If hooks aren't installed, run `pre-commit install` first or surface to the user.
- Force-push, rebase, or rewrite history.
- Skip signing or hooks under any circumstance.
- Edit `~/.gitconfig` or set committer identity. If `user.name` / `user.email` aren't set, surface to the user — guessing the identity is wrong.

## Failure modes — surface, don't silently swallow

- Working tree contains files the user may not want committed (`.env`, large binaries, secrets) → list them, ask.
- No commit-message convention detectable AND no recent commits to mimic → ask the user for preferred format.
- Required task ID can't be resolved → ask before defaulting.
- Pre-commit hook fails repeatedly on the same issue → stop, root-cause, do not loop.
- `git push` rejected (non-fast-forward, branch protection, etc.) → show the error verbatim; do not `--force`.
- `gh auth status` fails after attempted bootstrap → stop, show the error, ask the user to run `gh auth login` manually.

## Sharing this skill

This file is the entire skill. To share it with a teammate:

1. Send them this `SKILL.md`.
2. They drop it at `~/.claude/skills/qc/SKILL.md` (create the dir if needed).
3. Next Claude Code session, `/qc` is available globally on their machine.

Or commit a copy at `<your-project>/.claude/skills/qc/SKILL.md` so anyone working in that project gets it project-locally without installing globally.

