---
name: git-publish
description: >
  Offers to git init/commit the current project and, optionally, create a GitHub repo
  (via `gh`) and push — always behind two explicit confirmations, one for the local
  commit and one for the remote create+push. Use right after a project was just
  generated by `/init-project`, right after `java-spring-boot-developer` reports a
  feature implemented successfully, when `/new-feature` ends with an approved spec, or
  whenever the user asks to create a git repo, commit, or push the current project.
allowed-tools: Bash, AskUserQuestion
---

# `git-publish` — offer to commit and push, with confirmation

## Why this is a skill (Form 1) and not Form 2

Two other pieces need to chain into this one by name: `project-initializer` (after
`/init-project` finishes) and `/new-feature` (after `java-spring-boot-developer` reports
success). `disable-model-invocation: true` (Form 2) blocks exactly that — the model
can't call a Form 2 skill via the `Skill` tool at all, only a human typing `/git-publish`
can, the same restriction `java-spring-boot-developer.md` documents for why it can't
re-invoke `java-patterns`. Precedent: `@.claude/decisions/0007-pipeline-skills-invocation.md`
(D17) — the five `/new-feature` pipeline skills stay without `disable-model-invocation`
for the same reason, and rely on an entry guard in the body instead of the flag. This
file's guard is the two `AskUserQuestion` gates below: no git side effect ever runs
without an explicit yes, regardless of what triggered the invocation.

Form 3 (agent) was rejected: the confirmation dialogue with the user is the heart of
this task, the context it needs fits entirely in this file, and the final output is
short — all three fail the counter-test in `references/decision-matrix.md` § 5.

Decision record: `@.claude/decisions/0034-git-publish-skill.md`.

## Contract

**Input (optional, from the invoking context):** a short description of what was just
done — e.g. "initial scaffold, blueprint hexagonal, maven, features: rest,jpa" or
"UC-001-order: order management feature". Used to build the commit message. No input →
generic message from what `git status` shows.

**Input (optional): paths to stage.** When the invoking context names paths — `/new-feature`
does, for an approved spec not implemented yet (`docs/use-cases/UC-NNN-<slug>/`,
`docs/use-cases/BACKLOG.md`) — gate 1 lists and stages **only** those paths. Anything else
dirty in the tree stays unstaged and is named in the report.

**Reads:** `git status`, `git remote -v`, `gh auth status`. Nothing in the project files.

**Writes:** `.git/` of the current project, and — only after the second confirmation —
a new GitHub repository via `gh repo create`, or a push to a remote the user names.
Never writes project source files.

**Integration:**
- Invoked by `project-initializer` (agent) right after a green build, via the `Skill`
  tool, with a scaffold summary as context.
- Invoked by `/new-feature` at its end: after `java-spring-boot-developer` reports success,
  with the UC name/summary as context; or, when the user declines implementing now, with
  the approved spec's paths as the only paths to stage.
- Invocable directly by the user (`/git-publish`, or by asking in plain language).

## Procedure

### 1 · Read state, never assume it

```bash
git rev-parse --is-inside-work-tree 2>/dev/null && echo TRACKED || echo UNTRACKED
git status --porcelain 2>/dev/null
git remote -v 2>/dev/null
git log -1 --oneline 2>/dev/null
```

Three states, three different phrasings for gate 1:

| State | Gate 1 asks |
|---|---|
| `UNTRACKED` (no `.git`) | "Initialize git and create the first commit?" |
| `TRACKED`, dirty (`git status --porcelain` non-empty) | "Commit these changes now?" |
| `TRACKED`, clean | Skip gate 1 — nothing to commit. Go straight to gate 2 only if there are unpushed commits or no remote configured; otherwise report "nothing to do" and stop |

### 2 · Gate 1 — local commit

`AskUserQuestion`, options: **Yes, commit now** / **No, skip**. "No" stops here — report
what would have been committed and don't touch anything.

On "Yes":

1. `git init` only if `UNTRACKED`.
2. Show `git status` and scan the file list for anything that looks like a secret before
   staging — `.env`, `*.pem`, `*.key`, `credentials*`, `*secret*`. If any match, stop and
   ask the user to confirm or exclude them; never stage a likely secret silently.
3. `git add -A` (respecting `.gitignore`, already generated by the Initializr) — or, when the
   input named paths, `git add -- <paths>` with only the paths that exist, and nothing else.
4. `git commit -m "<message>"` — build the message from the input context following
   Conventional Commits: `chore: initial project scaffold — <blueprint/build/features>`
   for a project-initializer call, `feat(UC-NNN-slug): <summary>` for a `/new-feature`
   call after the executor, `docs(UC-NNN-slug): approved spec` for a docs-only one, or a generic `chore: commit pending changes` with no context. Always append:

   ```
   Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
   ```

### 3 · Gate 2 — remote create/push

Only reached after gate 1 commits successfully, or when `TRACKED`+clean with unpushed
commits/no remote. `AskUserQuestion`, options:

- **Create a new GitHub repo (gh) and push** — needs `gh`. Run `gh auth status` first;
  not authenticated → report the exact command to fix it (`gh auth login`) and stop,
  never attempt to log in on the user's behalf. Ask visibility (public/private) as part
  of the same question. Then:
  ```bash
  gh repo create <artifactId> --<public|private> --source=. --remote=origin --push
  ```
- **Push to an existing remote** — ask for the URL if `git remote -v` showed none;
  `git remote add origin <url>` (or `set-url` if `origin` exists and the user confirms
  overwriting it), then `git push -u origin <current-branch>`.
- **Skip, keep local only** — stop, report the local commit is done and nothing was
  pushed.

Never `git push --force`, never `--no-verify`, never delete an existing remote without
the user naming that as the explicit choice.

### 4 · Report

```
✅ Committed <short-hash> — "<message>"
<✅ Pushed to <remote-url> (branch <name>) | ⏭️ Local only, not pushed>
```

## Failure modes

**`gh` missing or not authenticated:**
```
❌ gh not authenticated. Run `gh auth login`, then re-invoke this skill to push.
Local commit is already done — nothing lost.
```

**Suspicious file staged:**
```
⚠️ .env matched the secret-file scan. Excluded from `git add`.
Add it to .gitignore, or confirm explicitly if it must be committed.
```
