Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"name": "pr-flow",
"source": "./plugins/pr-flow",
"description": "PR review feedback loop for Claude Code. Create PRs with readiness checks, commit + push + trigger @claude review, inspect status, and work through review issues interactively.",
"version": "1.2.1"
"version": "1.2.2"
}
]
}
2 changes: 1 addition & 1 deletion plugins/pr-flow/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "pr-flow",
"description": "PR review feedback loop for Claude Code. Create PRs with readiness checks, commit + push + trigger @claude review, inspect status, and work through review issues interactively.",
"version": "1.2.1",
"version": "1.2.2",
"author": {
"name": "gering"
},
Expand Down
6 changes: 3 additions & 3 deletions plugins/pr-flow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ If a clear pattern emerges (e.g. 18 of 20 last PRs were squashed), `/merge` sugg

### Force-push safety

**What it does.** Any force-push inside `/rebase` uses `--force-with-lease`, never `--force`. If the remote advanced since the last fetch (e.g., a teammate pushed), the push fails fast with a clear message.
**What it does.** Any force-push inside `/rebase` uses `--force-with-lease`, never `--force`. If the remote advanced since the last fetch (e.g., a teammate pushed), the push fails fast with a clear message. Whether `/rebase` asks first depends on risk: when the files changed on base and on the branch don't overlap, it rebases + force-pushes immediately (invoking the skill is the authorization); when files overlap, it presents a selection menu (rebase / show diff / leave as-is) before touching anything.

**Why it matters.** `--force` overwrites teammate commits silently. `--force-with-lease` is the safer default. The skills never bypass it, even under `--no-verify` scenarios — if a hook fails, we investigate, we don't skip.
**Why it matters.** `--force` overwrites teammate commits silently. `--force-with-lease` is the safer default. And a conflict-free catch-up rebase shouldn't cost a confirmation round-trip — prompts are reserved for cases where judgment is actually needed.

### Structured review output

Expand Down Expand Up @@ -186,7 +186,7 @@ Each skill runs a preflight check and stops with a clear message if requirements

## Design principles

- **Interactive by default** — no silent commits, pushes, or fixes without user confirmation. The one explicit opt-in exception is `/cycle --loop`: the invocation authorizes the autonomous cycle, and even then the final squash asks first
- **Interactive by default, silent when risk-free** — no silent commits or fixes without user confirmation; decisions are real selection menus, not free-text prompts. Two deliberate exceptions where the invocation itself is the authorization: `/cycle --loop` (autonomous cycle; the final squash still asks) and `/rebase` with zero file overlap (conflict-free catch-up, aborts cleanly if a conflict appears anyway)
- **Read-only where it matters** — `/check` never mutates anything
- **User stays in control** — `/fix` does not auto-trigger `/cycle`; you decide when to re-push (or hand the wheel to `/cycle --loop` deliberately)
- **Root cause over workaround** — `/merge` refuses `--admin` bypass. A failing required check is a signal to fix the check, not to skip it
Expand Down
1 change: 1 addition & 0 deletions plugins/pr-flow/skills/cycle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Strip the flags first; whatever is left over is the commit message.

3. **Handle uncommitted changes**:
- Run: `git status --porcelain`
- **Guard against an unresolved merge state first**: if any entry carries a conflict status (`UU`, `AA`, `DD`, or any `U`/`U` combination — e.g. markers already in the tree from a merge/rebase/stash-pop you ran yourself before invoking `/cycle`), STOP. Do not `git add -A` over conflict markers. Report the conflicted files and tell the user to resolve them, then re-run `/cycle`. (Note: a stash-pop conflict from the step-2 rebase makes `/rebase` return non-cleanly, so step 2 already stops before here — this guard covers the pre-existing-markers case.)
- If changes exist:
- If `$ARGUMENTS` provided, use as commit message
- Otherwise, generate a concise commit message based on the changes (show diff first)
Expand Down
20 changes: 11 additions & 9 deletions plugins/pr-flow/skills/merge/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ user_invocable: true

**No sanity-check prompts.** When every preflight check is ✅ (or ➖ N/A), execute the merge immediately without a final "Proceed? / Merge ausführen?" confirmation. The user ran `/merge` — that IS the authorization.

**Only ask when a decision is needed.** A prompt is warranted only for: draft-to-ready flip (step 1), rebase confirmation (delegated to `/rebase`), merge-method when ambiguous (step 9), WIP commit cleanup (step 10), and the three-way f/m/a decision when ⚠️ warnings exist (step 13). Cleanup defaults (step 12) apply silently unless something is protected/unusual.
**Only ask when a decision is needed.** A prompt is warranted only for: draft-to-ready flip (step 1), rebase confirmation (delegated to `/rebase`, step 3), merge-method when ambiguous (step 9), WIP commit cleanup (step 10), and the three-way f/m/a decision when ⚠️ warnings exist (step 13). Cleanup defaults (step 12) apply silently unless something is protected/unusual.

**If ⚠️ warnings exist**, present the three-way prompt in step 13 **exactly once**. Never stack it with additional confirmation questions.

Expand All @@ -35,24 +35,26 @@ user_invocable: true
- `n`: stop
- Store `PR_NUMBER`, `BASE_BRANCH`, `HEAD_BRANCH`.

2. **Rebase check** — delegate to `/rebase --no-poll --auto`:
2. **Local cleanliness** (run BEFORE the rebase — the step-3 rebase must not mutate a dirty tree):
- `git status --porcelain` — if anything: stop "Commit or stash local changes before merging (`/cycle` handles commit + push)."
- `git log @{u}..HEAD --oneline 2>/dev/null` — if unpushed commits: stop "Push local commits first (`/cycle` handles this)."
- Why first: `/rebase --auto` (step 3) auto-stashes a dirty tree and may force-push the rebased branch. If `/merge` rebased+pushed and only then rejected the dirty tree, it would have burned a force-push + CI run for a merge that never happens. Gating cleanliness here means the rebase only runs on a tree `/merge` will actually accept.

3. **Rebase check** — delegate to `/rebase --no-poll --auto`:
- Run `/rebase` with **both** `--no-poll` and `--auto`. Rationale:
- `--no-poll`: polling would delay the merge — any review should already have been handled by a prior `/cycle`.
- `--auto`: the user invoked `/merge`; that invocation authorizes rebase + force-push as preflight. Asking again would be a redundant second prompt.
- The working tree is already clean (step 2), so `/rebase --auto` won't stash/pop anything.
- `/rebase --auto` still aborts cleanly on conflicts (no destructive behavior skipped — only the routine confirmation is skipped).
- If `/rebase` aborts due to conflicts → stop this skill: "Merge requires up-to-date branch. Resolve conflicts, then re-run `/merge`."

3. **Local cleanliness**:
- `git status --porcelain` — if anything: stop "Commit or stash local changes before merging."
- `git log @{u}..HEAD --oneline 2>/dev/null` — if unpushed commits: stop "Push local commits first (`/cycle` handles this)."

4. **Refresh PR state** (GitHub recomputes after push/rebase):
- Wait ~5s if anything was pushed/rebased in step 2
- Wait ~5s if anything was pushed/rebased in step 3
- Re-run: `gh pr view <N> --json mergeable,mergeStateStatus,reviews`
- Interpret `mergeStateStatus`:
- `CLEAN` → ✅ ready
- `HAS_HOOKS` → ✅ ready (hooks will run)
- `BEHIND` → ⚠️ base moved again since step 2, re-run `/rebase`
- `BEHIND` → ⚠️ base moved again since step 3, re-run `/rebase`
- `BLOCKED` → ⚠️ required reviews or checks missing (step 5 + 6 will detail)
- `CONFLICTING` → ❌ stop: "Merge conflicts. Resolve manually (`git merge origin/<BASE>` or `git rebase`)"
- `UNSTABLE` → ⚠️ CI not green but branch protection allows merge — treat as warning
Expand Down Expand Up @@ -138,8 +140,8 @@ user_invocable: true
<URL>

── Preflight ──
✅ Branch up-to-date with main
✅ Local clean (no uncommitted/unpushed)
✅ Branch up-to-date with main

── GitHub state ──
✅ Mergeable: CLEAN
Expand Down
Loading
Loading