---
name: opsx-branch-flow
description: Worktree and delivery discipline for OpenSpec work in this repo — /opsx:propose commits or parks whatever is loose on master, then opens an isolated worktree branched from it; apply implements inside that worktree, verifies with the test suite, then walks commit+push → PR → merge one confirmation at a time. Use whenever an opsx/openspec propose, update, or apply workflow starts, resumes, or completes.
allowed-tools: Bash(git:*), Bash(gh:*), Bash(make:*)
---

Every OpenSpec change in this repo is developed in its own **git worktree**, branched from
`master`. `/opsx:propose` opens it; `/opsx:apply` implements inside it; `master` only moves at
the end of a gated delivery chain the user walks deliberately.

This skill owns the git side. It does not replace `/opsx:propose` or `/opsx:apply` — it wraps
them.

## The three moments

| Moment | What happens |
|---|---|
| `/opsx:propose`, before its first command | Clear the base, open the worktree (§A) |
| `/opsx:apply` or `/opsx:update`, before editing | Make sure you are inside it (§B) |
| `/opsx:apply` reports **all tasks complete** | Verify → commit+push → PR → merge (§C) |

Base branch is **`master`** — also this repo's GitHub default, so `origin/HEAD` tracks it.

Names, both derived from the kebab-case OpenSpec change name:

- worktree + branch: `change/<change-name>`
- worktree path: `.claude/worktrees/change/<change-name>`

One worktree per change, reused across sessions.

## §A — Clear the base, then open the worktree (propose only)

Only `/opsx:propose` opens a worktree. Apply never does.

### 1. The base must be committed first

```bash
git status --porcelain
```

The worktree branches from `master`'s **committed** HEAD, so anything staged or unstaged here
stays behind in the base checkout and will not be part of the change. If the output is not
empty, stop and ask with `AskUserQuestion` before going further:

- `Commit them on master first` *(Recommended)* — show the file list, agree a message, commit on
  `master`. The worktree then starts from a base that includes them.
- `Leave them behind` — proceed; the edits stay in the base checkout, uncommitted, and the
  change will not contain them.
- `Stop — I'll sort it out` — abort the propose entirely.

Never stash, reset, checkout-discard, or clean on your own judgement.

### 2. Stand on `master` and refresh it

```bash
git switch master
git pull --ff-only
```

This is what makes `master` the base: `.claude/settings.json` sets `worktree.baseRef: head`, so
the worktree branches from the session's current HEAD. That setting is deliberate — with the
`fresh` default it would branch from `origin/master` and **lose the commit you just made in
step 1**.

The pull is best effort — this repo may be offline. If it fails, say one line and continue from
local `master`. Never `--rebase`, `--force`, or reset to repair a failed pull.

### 3. Open it

If `.claude/worktrees/change/<change-name>` already appears in `git worktree list`, do not
create a second one — enter it with
`EnterWorktree(path: ".claude/worktrees/change/<change-name>")` and skip to step 4.

Otherwise: `EnterWorktree(name: "change/<change-name>")`

Then confirm the branch point, since a mis-set `worktree.baseRef` would silently branch from
somewhere else:

```bash
git merge-base --is-ancestor master HEAD
```

Non-zero means the branch point is wrong. Stop and report it — do not rebase to "fix" it.

### 4. Announce, then plan

`Worktree: .claude/worktrees/change/<change-name> (branch change/<change-name>, from master)`

Run propose as normal, then commit its artifacts inside the worktree:

```bash
git add openspec/ && git commit -m "plan(<change-name>): openspec artifacts"
```

Planning artifacts only. If propose touched project code, that is a bug in the propose run —
report it rather than committing it.

Leave the worktree in place when propose ends. Do not call `ExitWorktree` — the user comes back
to it for apply.

## §B — Implementing inside it (apply, update)

1. `git rev-parse --show-toplevel` — if it already ends in
   `.claude/worktrees/change/<change-name>`, continue.
2. If not, enter it: `EnterWorktree(path: ".claude/worktrees/change/<change-name>")`.
3. If no such worktree exists — the change predates this workflow, or the user removed it — say
   so and open one per §A before editing anything.

Implement the tasks. Commit as you go, one commit per task or coherent group, with the task
number:

```bash
git add -A && git commit -m "<change-name>: task 3.2 — <short description>"
```

End commit messages with:

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

## §C — Delivery, after every task is complete

Only when apply reports **all tasks complete**. If it paused, was blocked, or left checkboxes
unticked, report what remains and start none of this.

**How to call `gh`.** Installed (2.97.0) but **not on PATH** — bare `gh` fails in both shells.
Always use the full path:

```bash
"/c/Program Files/GitHub CLI/gh.exe" auth status
```

PowerShell needs the call operator: `& "C:\Program Files\GitHub CLI\gh.exe" ...`

If `auth status` reports no logged-in host, stop before the PR step and ask the user to run
`gh auth login` themselves — it is interactive and it is authentication, so never run it for
them and never handle the credentials. Steps 1 and 2 below do not need `gh`.

### Step 1 — verify, and get a decision on anything unproven

Run the suite before offering to push anything:

```bash
make test
make lint
```

Report the real result. Then, **if the change is not fully proven** — tests failed, tests were
skipped, the suite could not run at all (Docker down, `make` missing), or tasks shipped code
with no test covering it — do not quietly continue. Say precisely which of those it is, then
ask with `AskUserQuestion`:

> Question: `<N> tests failing / the suite could not run. Continue to commit and push anyway?`
> Header: `Unproven`
> Options:
> - `Stop — fix it first` *(Recommended)* — nothing is committed or pushed; go back to the code.
> - `Continue anyway` — push with the gap recorded.

On `Continue anyway`, carry the gap forward in writing — a line in the commit body and in the
PR body saying what was failing or unrun. An ignored failure that leaves no trace is how it
reaches `master` unnoticed.

If everything passes, say so and go straight to step 2 without asking.

### Step 2 — commit and push

Commit whatever is still uncommitted, then push:

```bash
git add -A && git commit -m "<change-name>: <summary>"
git push -u origin change/<change-name>
```

### Step 3 — pull request, with the warning

Put the warning in the message itself, not only in an option description:

> ⚠️ **Please check the full app is working properly before this goes to `master`.** The test
> suite is not the app — for anything touching the customer path that means `make dev` and a
> real pass through it, or `make walkthrough` end to end.

> Question: `Open a PR into master for change/<change-name>?`
> Header: `PR?`
> Options:
> - `Not yet — I'll check the app first` *(Recommended)* — nothing is opened; the branch is
>   pushed and waiting.
> - `Create the PR` — open it and stop there, so it can be reviewed on GitHub.
> - `Create the PR and merge it` — open and merge into `master` in one go. Only pick this having
>   already checked the app.

```bash
"/c/Program Files/GitHub CLI/gh.exe" pr create --base master --head change/<change-name> --title "<change-name>: <summary>" --body "<body>"
```

`--base master` stays explicit even though it is the repo default, so a changed default cannot
silently retarget the PR.

Body: what the change does and why, from the change's `proposal.md`, the task count, and any
gap carried over from step 1. End it with:

```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
```

If `gh` fails — auth, network, binary moved — don't reinstall or reconfigure it. Hand the user
the compare link and treat the step as done once they say the PR exists:

`https://github.com/modmurad/wedding-rsvp/compare/master...change/<change-name>?expand=1`

A code review is available but is not part of this chain: mention that `/code-review <PR#>` can
be run on the open PR, and run it only if asked.

### Step 4 — merge

Merge **only** on `Create the PR and merge it` from step 3, or on a later explicit request. If
they chose `Create the PR`, stop after opening it and say the merge is theirs to trigger.

```bash
"/c/Program Files/GitHub CLI/gh.exe" pr merge <PR#> --merge
```

A merge commit, not squash and not rebase — the change keeps a visible boundary in history. If
`gh` is unusable, point the user at the PR page rather than merging locally and pushing
`master`.

On conflict: report the conflicting files and stop. Do not resolve conflicts unattended.

After a clean merge, report it, then offer — do not perform — the cleanup: deleting the remote
branch and `ExitWorktree(action: "remove")` for the local worktree. Then suggest
`/opsx:archive`.

## Never

- Commit, push, open a PR, or merge without the yes that specifically authorizes that step.
- Report a push or merge as done when the command failed, or call a suite green when it was
  skipped or never ran.
- Reorder the steps, or fold the verify warning into the PR question.
- Write artifacts or code in the base checkout while a worktree exists for the change.
- `git reset --hard`, `git checkout -- .`, `git clean`, or force-push to recover from a git
  problem — report it instead.
- Rebase or amend once the branch is pushed.
- Run `gh auth login`, or handle GitHub credentials in any form.
- Call `ExitWorktree` proactively — only when the user asks.
