---
title: "Create Pr"
description: "Creates GitHub pull requests with pre-flight validation, conventional title formatting, and structured summary generation. Runs parallel checks (tests, lint, type-check, security) before opening. Supports feature, bugfix, refactor, and hotfix PR types with milestone assignment via gh CLI. Invoke only if the operator named it; an everyday `gh pr create` stays plain tooling. Use when opening PRs or submitting code for review."
canonical: "https://orchestkit.yonyon.ai/docs/reference/skills/create-pr"
---

# Create Pr

Creates GitHub pull requests with pre-flight validation, conventional title formatting, and structured summary generation. Runs parallel checks (tests, lint, type-check, security) before opening. Supports feature, bugfix, refactor, and hotfix PR types with milestone assignment via gh CLI. Invoke only if the operator named it; an everyday `gh pr create` stays plain tooling. Use when opening PRs or submitting code for review.

<span className="badge badge-blue">Command</span> <span className="badge badge-yellow">medium</span>

```bash title="Invoke"
/ork:create-pr
```

<ContextualSkillSidebar slug="create-pr" />

> **Create Pr** Creates GitHub pull requests with pre-flight validation, conventional title formatting, and structured summary generation. Runs parallel checks (tests, lint, type-check, security) before opening. Supports feature, bugfix, refactor, and hotfix PR types with milestone assignment via gh CLI. Invoke only if the operator named it; an everyday `gh pr create` stays plain tooling.


# Create Pull Request

Comprehensive PR creation with validation. All output goes directly to GitHub PR.

## Quick Start

```bash
create-pr
create-pr "Add user authentication"
```

> **CC ≥ 2.1.119 multi-host note (M122):** PR creation works against GitHub, GitLab, Bitbucket, and GitHub Enterprise. Detect the target host from the configured remote (`git remote -v`) and branch on the host family for the right CLI:
>
> | Host family | CLI |
> |---|---|
> | github / github-enterprise | `gh pr create` (with `GH_HOST=<host>` for GHE) |
> | gitlab / gitlab-self | `glab mr create` |
> | bitbucket | `bb pr create` |
>
> Custom enterprise URLs: `prUrlTemplate` setting (see `src/skills/configure/` and `src/skills/chain-patterns/references/pr-from-platform.md`).

## Argument Resolution

```python
TITLE = "$ARGUMENTS"  # Optional PR title, e.g., "Add user authentication"
# If provided, use as PR title. If empty, generate from branch/commits.
# $ARGUMENTS[0] is the first token (CC 2.1.59 indexed access)
```

## Base Branch Resolution

Derive the base branch from the remote. Never hardcode `dev` or `main`; repos differ.

```bash
BASE=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')
BASE=${BASE:-main}   # ref missing (fresh/shallow clone)? run: git remote set-head origin -a
```

Every `$BASE` below refers to this value.

### Stacked on another open PR

When the work depends on an open PR ("a PR that depends on PR #50"), that PR's head branch is
the base, not `$BASE`:

```bash
PARENT=50
PARENT_BRANCH=$(gh pr view "$PARENT" --json headRefName -q .headRefName)
git fetch origin && git rebase "origin/$PARENT_BRANCH"   # pre-flight: sync with the parent
gh pr create --base "$PARENT_BRANCH" --title "$TYPE(#$ISSUE): ... [2/3]" \
  --body "Stacked on #$PARENT. Do not merge into the parent branch."
```

Phase 1's guard still applies: never open from `main` or `dev`, never with a dirty tree. Record
`git rev-parse "origin/$PARENT_BRANCH"` in the body; after the parent is squash-merged that sha
is the upstream argument that `git rebase --onto "origin/$BASE" <that-sha> <branch>` needs, and
the branch itself is gone. Squash recovery, depth cap, draft trap: `Read("references/stacked-pr.md")`.

---

## STEP 0: Verify User Intent

**BEFORE creating tasks**, clarify PR type:

```python
AskUserQuestion(
  questions=[{
    "question": "What type of PR is this?",
    "header": "PR Type",
    "options": [
      {"label": "Feature (Recommended)", "description": "Full validation: security + quality + tests"},
      {"label": "Bug fix", "description": "Focus on test verification"},
      {"label": "Refactor", "description": "Code quality review, skip security"},
      {"label": "Quick", "description": "Skip validation, just create PR"}
    ],
    "multiSelect": false
  }]
)
```

**Based on answer, adjust workflow:**
- **Feature**: Full Phase 2 with 3 parallel agents + local tests
- **Bug fix**: Phase 2 with test-generator only + local tests
- **Refactor**: Phase 2 with code-quality-reviewer only + local tests
- **Quick**: Skip Phase 2, jump to Phase 3

### Optional pre-flight: `claude ultrareview` (CC 2.1.120+, #1542)

If `claude ultrareview --help` succeeds, optionally run it before opening the PR and surface findings in the PR body's `## Pre-flight` section. The CLI subcommand returns structured `--json` output that can be filtered to high/medium severity for the body and full results posted as a follow-up comment.

```bash
if claude ultrareview --help >/dev/null 2>&1; then
  claude ultrareview "origin/$BASE..HEAD" --json > /tmp/ultra.json
  # Bucket by severity, put HIGH in PR body, MEDIUM/LOW as comment
fi
```

Skip on CC &lt; 2.1.120 (the subcommand doesn't exist there). The `.github/workflows/ultrareview.yml` workflow runs the same command on PR open as a backstop, so this pre-flight is purely a feedback-loop accelerant.

### Progressive Output (CC 2.1.76)

Output results **incrementally** during PR creation:

| After Step | Show User |
|------------|-----------|
| Pre-flight | Branch status, remote sync result |
| Each agent | Agent validation result as it returns |
| Tests | Test results, lint/typecheck status |
| PR created | PR URL, CI status link |

For feature PRs with 3 parallel agents, show each agent's result **as it returns**, don't wait for all agents before running local tests.

---

## STEP 1: Create Tasks (MANDATORY)

**BEFORE doing ANYTHING else, create tasks to track progress:**

```python
# 1. Create main task IMMEDIATELY
TaskCreate(subject="Create PR for {branch}", description="PR creation with validation", activeForm="Creating pull request")

# 2. Create subtasks for each phase
TaskCreate(subject="Pre-flight checks", activeForm="Running pre-flight checks")        # id=2
TaskCreate(subject="Run validation agents", activeForm="Validating with agents")       # id=3
TaskCreate(subject="Run local tests", activeForm="Running local tests")                # id=4
TaskCreate(subject="Create PR on GitHub", activeForm="Creating GitHub PR")             # id=5
TaskCreate(subject="Generate PR playground", activeForm="Generating playground")       # id=6

# 3. Set dependencies for sequential phases
TaskUpdate(taskId="3", addBlockedBy=["2"])  # Agents need pre-flight to pass
TaskUpdate(taskId="4", addBlockedBy=["3"])  # Tests run after agent validation
TaskUpdate(taskId="5", addBlockedBy=["4"])  # PR creation needs tests to pass
TaskUpdate(taskId="6", addBlockedBy=["5"])  # Playground after PR (needs title/summary)

# 4. Update status as you progress
TaskUpdate(taskId="2", status="in_progress")  # When starting
TaskUpdate(taskId="2", status="completed")    # When done — repeat for each subtask
```

---

## Workflow

### Phase 1: Pre-Flight Checks

Load: `Read("rules/preflight-validation.md")` for the full checklist.

```bash
BRANCH=$(git branch --show-current)
[[ "$BRANCH" == "dev" || "$BRANCH" == "main" ]] && echo "Cannot PR from dev/main" && exit 1
[[ -n $(git status --porcelain) ]] && echo "Uncommitted changes" && exit 1

git fetch origin
git rev-parse --verify "origin/$BRANCH" &>/dev/null || git push -u origin "$BRANCH"
```

### Phase 2: Parallel Validation (Feature/Bug fix PRs)

Launch agents in ONE message. Load `Read("references/parallel-validation.md")` for full agent configs.

| PR Type | Agents to launch |
|---------|-----------------|
| Feature | security-auditor + test-generator + code-quality-reviewer |
| Bug fix | test-generator only |
| Refactor | code-quality-reviewer only |
| Quick | None |

After agents complete, run local validation:

```bash
# Adapt to project stack
npm run lint && npm run typecheck && npm test -- --bail
# or: ruff check . && pytest tests/unit/ -v --tb=short -x
```

### Phase 3: Gather Context

```bash
BRANCH=$(git branch --show-current)
ISSUE=$(echo "$BRANCH" | grep -oE '[0-9]+' | head -1)
git log --oneline "origin/$BASE..HEAD"
git diff "origin/$BASE...HEAD" --stat
```

### Phase 3b: Agent Attribution (automatic)

Before creating the PR, check for the branch activity ledger at `.claude/agents/activity/{branch}.jsonl`.
If it exists, generate agent attribution sections for the PR body:

1. Read `.claude/agents/activity/{branch}.jsonl` (one JSON object per line, full branch history)
2. Deduplicate by agent type (keep the entry with longest duration for each agent)
3. Generate the following sections to append to the PR body:
   - **Badge row**: shields.io badges for agent count, tests generated, vulnerabilities
   - **Agent Team Sheet**: Markdown table with Agent, Role, Stage (Lead/⚡ Parallel/Follow-up), Time
   - **Credits Roll**: Collapsible `<details>` section grouped by execution stage (Lead/Parallel/Follow-up)
4. Each agent entry has: `agent` (type), `stage` (0=lead, 1=parallel, 2=follow-up), `duration_ms`, `summary`

If the ledger doesn't exist or is empty, skip this step, create PR normally.

> **CC 2.1.183, `attribution.sessionUrl`:** Web and Remote Control sessions append a claude.ai session link to the PR body. For public repos where that link should not be exposed, set `attribution.sessionUrl: false` (`/config attribution.sessionUrl=false`) before creating the PR. ork's agent-attribution sections above are independent of this setting.

### Phase 3c: CodeRabbit CLI pre-review (before `gh pr create`)

CodeRabbit PR reviews are rate limited per GitHub identity (Essentials: 5/hour refill under
30 reviews in 7 days, 1/hour at 60+), and every push to an open PR spends one. The CLI has a
separate allowance, so review the branch diff locally first. Advisory only, never blocks.

```bash
CR_BIN=$(command -v coderabbit || true)   # `cr` is also helm chart-releaser: accept it only by --help
[ -z "$CR_BIN" ] && command -v cr >/dev/null 2>&1 && grep -q CodeRabbit <<<"$(cr --help 2>&1)" && CR_BIN=cr
CR_OUT=$(mktemp "${TMPDIR:-/tmp}/cr-prereview.XXXXXX")
[ -n "$CR_BIN" ] && perl -e 'alarm shift; exec @ARGV' 600 \
  "$CR_BIN" review --agent --base "origin/$BASE" </dev/null > "$CR_OUT" 2>&1 \
  || echo "CodeRabbit CLI pre-review skipped (not installed, not signed in, timed out, or errored): $CR_OUT"
```

Fix clear defects in lines this branch changed, commit, re-run Phase 2 local validation, then go
to Phase 4 without re-running the CLI. Omitting `--use-credits` avoids spend only when the org's usage-based add-on is On demand or Off; Automatic bills overages.
Detection, degrade table, billing modes, triage: `Read("references/coderabbit-cli-prereview.md")`.

### Phase 4: Create PR

Follow `Read("rules/pr-title-format.md")` and `Read("rules/pr-body-structure.md")`. Use HEREDOC pattern from `Read("references/pr-body-templates.md")`.

Include agent attribution sections (from Phase 3b) after the Test Plan section in the PR body.

```bash
TYPE="feat"  # Determine: feat/fix/refactor/docs/test/chore
gh pr create --base "$BASE" \
  --title "$TYPE(#$ISSUE): Brief description" \
  --body "$(cat <<'EOF'
## Summary
[1-2 sentence description]

## Changes
- [Change 1]
- [Change 2]

## Test Plan
- [x] Unit tests pass
- [x] Lint/type checks pass

## Agent Team Sheet
| Agent | Role | Stage | Time |
|-------|------|-------|------|
| 🏗️ **backend-system-architect** | API design | Lead | 2m14s |
| 🛡️ **security-auditor** | Dependency audit | ⚡ Parallel | 0m42s |
| 🧪 **test-generator** | 47 tests, 94% coverage | ⚡ Parallel | 2m01s |

<details>
<summary><strong>🎬 Agent Credits</strong> — 3 agents collaborated on this PR</summary>

**Lead**
- 🏗️ **backend-system-architect** — API design (2m14s)

**⚡ Parallel** (ran simultaneously)
- 🛡️ **security-auditor** — Dependency audit (0m42s)
- 🧪 **test-generator** — 47 tests, 94% coverage (2m01s)

---
<sub>Orchestrated by <a href="https://github.com/yonatangross/orchestkit">OrchestKit</a> — 3 agents, 4m57s total</sub>

</details>

Closes #$ISSUE

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

### Phase 4b: PR Playground (REQUIRED, CI blocks without it)

On every non-bot PR, after Phase 4: `Read("references/pr-playground.md")` and follow it (archetype, commit to docs/\{branch-dir\}/, SHA-pinned Live Preview link in the body).

### Phase 5: Verify

```bash
PR_URL=$(gh pr view --json url -q .url)
echo "PR created: $PR_URL"
```

### CI Monitoring (CC 2.1.71)

After PR creation, schedule CI status monitoring.

Budget: 60 minutes of CI polling (a 5-minute cron, so at most 12 fires). A cron job repeats one fixed prompt and cannot count its own fires, so the prompt carries a wall-clock deadline computed once at creation; stop when every check has finished or at the deadline, whichever comes first, and CronDelete the job at either stop.

```python
# Guard: Skip cron in headless/CI (CLAUDE_CODE_DISABLE_CRON)
# if env CLAUDE_CODE_DISABLE_CRON is set, run a single check instead
# deadline_iso = creation time + 60 minutes, in UTC, filled in ONCE here
CronCreate(
  schedule="*/5 * * * *",
  prompt="Check CI for PR #{pr_number}: gh pr checks {pr_number} --repo {repo}. Deadline {deadline_iso}.
    All pass → CronDelete this job, report success.
    Any fail → CronDelete this job, alert with failure details.
    Still pending and `date -u` is at or past the deadline → CronDelete this job, report the pending checks."
)
```

### CodeRabbit harvest

After CI is green and before `gh pr merge` or `--auto`, when the repo has `.coderabbit.yaml` and the PR is not a draft: `Read("references/coderabbit-harvest.md")`.

### Handoff File

Write PR details for downstream skills:

```python
Write(".claude/chain/pr-created.json", JSON.stringify({
  "phase": "create-pr", "pr_number": N, "pr_url": "...",
  "branch": "...", "files_changed": [...], "related_issues": [...]
}))
```

---

## Rules

1. **NO junk files**: Don't create files in repo root
2. **Run validation locally**: Don't spawn agents for lint/test
3. **All content goes to GitHub**: PR body via `gh pr create --body`
4. **Keep it simple**: One command to create PR
5. **Respect the `gh` rate-limit hint (CC ≥ 2.1.116)**: when the Bash tool surfaces a GitHub rate-limit hint after a `gh` call (e.g. in a `/loop 5m gh pr checks …` watcher), **stop the loop and wait for reset**, do not blind-retry. See `ork:github-operations` for the full guidance.

## Next Steps (suggest to user after PR creation)

```
review-pr {PR_NUMBER}                # Self-review before requesting reviews
gh pr checks {PR_NUMBER}                  # one-shot status; the CI Monitoring cron polls up to its 60-minute deadline
/loop 1h gh pr view {PR_NUMBER} --json reviewDecision  # Monitor review status
```

## Verification Gate

Before claiming PR is ready, apply: `Read("${CLAUDE_PLUGIN_ROOT}/shared/rules/verification-gate.md")`. All tests must pass with fresh evidence. All CI checks green. No "should be fine."

## Quality Bar

Done means all of these hold:
- PR opened from a feature branch with a clean working tree against the correct base (`$BASE`, derived from `origin/HEAD`, or the detected host equivalent)
- Title uses conventional `type(#issue): ...` format matching the change
- Body carries Summary, Changes, and Test Plan sections; every closed issue has its own `Closes #N` keyword
- Pre-flight validation for the chosen PR type passed locally before creation (skipped only for the Quick type)
- CodeRabbit CLI pre-review ran once before `gh pr create` (Phase 3c), or its one-line skip reason is in the Test Plan
- Playground HTML exists at docs/\{branch-dir\}/*.html and the body links it, per `references/pr-playground.md` (required for non-bot PRs)
- `gh pr view --json url` returns the created PR URL
- On repos with `.coderabbit.yaml` and a non-draft PR: every CodeRabbit thread is resolved per `references/coderabbit-harvest.md`, with a reply naming the fix sha or the dismissal reason (`scripts/coderabbit-harvest.sh --unresolved` prints `[]`)

## Related Skills

- `ork:commit`: Create commits before PRs
- `ork:review-pr`: Review PRs after creation

## Picker fallback (#1795)

If the `AskUserQuestion` picker stalls (schema break, not a CC input bug, orchestkit#1795, now guarded by tests/skills/structure/test-askuserquestion-schema.sh), set `ORK_ASK_FALLBACK=text` before starting CC. The `lifecycle/ask-fallback-injector` hook injects a reminder telling the assistant to pose options inline as a numbered list and ask the user to reply with the option number.

## References

Load on demand with `Read("references/<file>")`:
| File | Content |
|------|---------|
| `references/pr-body-templates.md` | PR body templates |
| `references/parallel-validation.md` | Parallel validation agent configs |
| `references/ci-integration.md` | CI integration patterns |
| `references/multi-commit-pr.md` | Multi-commit PR guidance |
| `assets/pr-template.md` | PR template (legacy) |
| `scripts/coderabbit-harvest.sh` | CodeRabbit threads: read (one GraphQL call), `--reply`, `--resolve` |
| `references/coderabbit-zero-reviews.md` | Harvest returned zero: reviewed clean, or never reviewed? |
| `references/coderabbit-cli-prereview.md` | Phase 3c: why the PR review allowance is scarce, CLI command, degrade table, triage |
| `references/pr-playground.md` | Phase 4b: PR playground archetype, commit path, SHA-pinned Live Preview link (CI gate) |
| `references/coderabbit-harvest.md` | After CI green: read, refute, fix or dismiss, resolve every CodeRabbit thread |


---

## Rules (6)

### Branch Naming Conventions: MEDIUM


## Branch Naming Conventions

Branch names enable automated issue extraction and PR categorization.

**Pattern:** `<type>/<issue>-<short-description>`

**Examples:**
```
feat/123-user-profile-page
fix/456-login-race-condition
refactor/789-extract-auth-module
chore/update-deps
docs/improve-api-docs
```

**Incorrect:**
```
Feature/UserProfilePage
fix_login-bug
my-branch
bugfix/456_fix_the_login_race_condition_issue
```

**Correct:**
```
feat/123-user-profile-page
fix/456-login-race-condition
refactor/789-extract-auth-module
```

**Key rules:**
- Use lowercase with hyphens (no underscores, no camelCase)
- Include issue number when one exists
- Keep description to 3-5 words
- Type prefix matches conventional commit types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`
- Protected branches (`main`, `dev`) cannot be PR source branches

**Issue extraction:**
```bash
# Extract issue number from branch name
BRANCH=$(git branch --show-current)
ISSUE=$(echo "$BRANCH" | grep -oE '[0-9]+' | head -1)
```

**Edge cases:**
- No issue: omit number, `chore/update-deps`
- Multiple issues: use primary issue number, mention others in PR body
- Long descriptions: truncate, branch names are for identification, not documentation


### Diff Size Guidelines: MEDIUM


## Diff Size Guidelines

Smaller PRs get better reviews, merge faster, and are easier to revert.

**Size thresholds:**

| Lines changed | Category | Action |
|---------------|----------|--------|
| &lt; 50 | Tiny | Merge quickly |
| 50-200 | Small | Ideal PR size |
| 200-400 | Medium | Acceptable, consider splitting |
| 400-800 | Large | Split if possible |
| > 800 | Too large | Must split (except generated/vendored files) |

**Key rules:**
- Aim for under 400 lines of meaningful change
- Generated files (lockfiles, build output) don't count toward the limit
- One logical change per PR (same principle as atomic commits)
- If a feature requires 800+ lines, split into stacked PRs

**Incorrect:**
```bash
# Single PR with 1200 lines: refactor + feature + config changes
gh pr create --title "Add auth, refactor middleware, update config"
```

**Correct:**
```bash
# Split into 3 focused PRs
gh pr create --title "refactor: extract auth middleware"     # PR 1: 180 lines
gh pr create --title "feat: add JWT authentication"          # PR 2: 250 lines
gh pr create --title "chore: update auth config"             # PR 3: 40 lines
```

**Splitting strategies:**
- Separate infrastructure/config changes from feature code
- Split backend and frontend into separate PRs
- Extract refactoring into a prep PR, then add the feature
- Split by module/domain boundary

**When large PRs are acceptable:**
- Auto-generated code (migrations, protobuf, lockfiles)
- Bulk renames or reformatting (use a dedicated PR with no logic changes)
- Initial project scaffolding
- Dependency updates with lockfile changes

**Checking diff size:**
```bash
git diff dev...HEAD --stat          # File-level summary
git diff dev...HEAD --shortstat     # One-line total
```


### PR Body Structure: HIGH


## PR Body Structure

Every PR body must include standard sections. Use `gh pr create --body` with a HEREDOC for correct formatting.

**Required sections:**

1. **Summary**: 1-2 sentences describing what and why (not how)
2. **Changes**: Bulleted list of what changed (for quick scanning)
3. **Test Plan**: Checkbox list of verification steps taken
4. **Issue reference**: `Closes #N` to auto-close the linked issue on merge

**Optional sections (include when relevant):**

- **Breaking Changes**: Migration steps if behavior changes
- **Screenshots**: For UI changes
- **Deployment Notes**: Environment variables, migrations, infrastructure

**Key rules:**
- Always use `Closes #N` (not `Fixes` or `Resolves`) for consistency with CI auto-close
- End body with `Generated with [Claude Code](https://claude.com/claude-code)` footer
- Use `--body "$(cat <<'EOF' ... EOF)"` HEREDOC pattern for multi-line bodies
- Keep Summary focused on "why", the diff shows "what"
- Test Plan checkboxes should reflect actual verification, not aspirational items

**Incorrect:**
```markdown
## PR
Updated some files. Fixed the thing.
```

**Correct:**
```markdown
## Summary
Add JWT refresh token rotation to prevent token replay attacks.

## Changes
- Add refresh token rotation on each use
- Store token family for replay detection
- Add 7-day absolute expiry

## Test plan
- [x] Unit tests for token rotation
- [x] Integration test for replay detection

Closes #456
```

**Anti-patterns:**
- Empty PR body (even for tiny changes, include Summary + Test Plan)
- Copy-pasting the full diff into the body
- Generic "Updated files" without specifics
- Unchecked test plan items (means tests were not actually run)

Reference: See `references/pr-body-templates.md` for full template examples.


### PR Title Format: HIGH


## PR Title Format

PR titles must be concise, scannable, and follow conventional commit prefixes for automated tooling.

**Incorrect:**
```
Updated the user profile page to add avatar upload functionality and fixed a bug
```

**Correct:**
```
feat(#456): add user profile page with avatar upload
```

**Key rules:**
- Under 70 characters total (GitHub truncates at ~72)
- Use conventional prefix: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `ci`, `perf`
- Include issue number in scope when available: `feat(#123): ...`
- Use imperative mood: "add", "fix", "update" (not "added", "fixes", "updating")
- No trailing period
- Lowercase after prefix (unless proper noun)

**Prefix selection:**

| Prefix | Use when |
|--------|----------|
| `feat` | New user-facing functionality |
| `fix` | Bug fix for existing behavior |
| `refactor` | Code change with no behavior change |
| `docs` | Documentation only |
| `test` | Test additions or corrections |
| `chore` | Build, CI, dependency updates |
| `ci` | CI configuration changes |
| `perf` | Performance improvement |

**Edge cases:**
- Breaking changes: add `!` after scope, `feat(#123)!: redesign auth flow`
- Multiple issues: use primary issue, `fix(#123): resolve race condition`
- No issue: omit scope parens, `chore: update dependencies`


### Pre-Flight Validation: CRITICAL


## Pre-Flight Validation

Every PR creation must pass these checks before the `gh pr create` command runs.

**Checklist (all must pass):**

1. **Branch check**: Not on `main` or `dev`. Create a feature branch first.
2. **Clean working tree**: No uncommitted changes (`git status --porcelain` is empty). Commit or stash first.
3. **Remote push**: Branch exists on remote. Push with `-u` if needed.
4. **Tests pass**: Run the project's test suite. At minimum: unit tests with `-x` (fail fast).
5. **Lint clean**: No linting errors from the project's linter (ruff, eslint, etc.).
6. **Type check clean**: No type errors (mypy, tsc, pyright) if project uses type checking.
7. **No secrets**: No API keys, tokens, or credentials in the diff. Check with `git diff --cached` patterns.
8. **Visual-style clean**: PR title + body pass the Visual-Style lint (the CI check that has bitten non-bot PRs repeatedly). The body must use only the 12-glyph vocabulary; the classic trap is `✓`/`✗` (U+2713/U+2717), use the white-check / red-X instead. Run the SAME engine CI runs, against your drafted title + body file, BEFORE `gh pr create`:
   ```bash
   bin/check-pr-visual-style.sh --title "<your title>" --body-file /tmp/pr-body.md
   ```
   Clean here == clean in CI (both call `bin/validate-visual-style.py`), so it kills the red-CI round-trip.

**Key rules:**
- Run checks locally, not via agents (faster, no token cost)
- Fail fast: stop at first blocking error
- For "Quick" PR type, skip validation steps 4-7 but always check 1-3
- Report failures clearly with actionable fix instructions

**Incorrect:**
```bash
# Skip checks, create PR directly from main with uncommitted changes
gh pr create --title "fix: login bug"
# Result: PR from main branch, dirty working tree, CI fails
```

**Correct:**
```bash
# Run full preflight before creating PR
git status --porcelain                    # Must be empty
git branch --show-current                 # Must not be main/dev
npm run lint && npm run typecheck         # Must pass
npm test -- --bail                        # Must pass
bin/check-pr-visual-style.sh --title "fix: login bug" --body-file /tmp/pr-body.md  # Must pass
gh pr create --title "fix: login bug"     # Now safe to create
```

**Validation commands (adapt to project):**
```bash
# Universal checks
git status --porcelain              # Must be empty
git branch --show-current           # Must not be main/dev

# Python projects
ruff format --check . && ruff check .
pytest tests/unit/ -v --tb=short -x

# Node projects
npm run lint && npm run typecheck
npm test -- --bail
```

**Blocking vs warning:**
- Steps 1-3: **BLOCK**, Cannot proceed
- Steps 4-6: **BLOCK** for Feature/Bug fix, **WARN** for Quick
- Step 7 (secrets): **ALWAYS BLOCK**, No exceptions
- Step 8 (visual-style): **ALWAYS BLOCK**, it is a hard CI gate (bypass only via the `visual-style-override` label, used sparingly)


### Review Readiness Criteria: HIGH


## Review Readiness Criteria

Determine whether a PR should be created as draft or ready for review.

**Create as DRAFT when:**
- Tests are not yet passing
- Implementation is partial (WIP)
- You want early feedback on approach before finishing
- CI has not been verified locally

**Create as READY when:**
- All pre-flight checks pass (see `preflight-validation.md`)
- Self-review is complete (you read your own diff)
- PR body is filled out with Summary, Changes, Test Plan
- No TODO/FIXME comments in changed lines (unless intentional and explained)

**Incorrect:**
```bash
# Mark ready with failing tests, TODO comments, and debug statements
gh pr create --title "feat: add dashboard"
# Contains: console.log("DEBUG"), // TODO: fix later, tests skipped
```

**Correct:**
```bash
# Create as draft first, clean up, then mark ready
gh pr create --draft --title "feat: add dashboard"
# Fix all TODOs, remove debug statements, verify tests pass
gh pr ready
```

**Self-review checklist (do before marking ready):**
- [ ] Read the full diff (`git diff dev...HEAD`) as if reviewing someone else's code
- [ ] No debug statements (console.log, print, debugger) left in
- [ ] No commented-out code without explanation
- [ ] Variable/function names are clear
- [ ] No unrelated changes mixed in

**Key rules:**
- Use `gh pr create --draft` for draft PRs
- Convert draft to ready with `gh pr ready`
- Never force-request review on a draft PR
- If unsure, default to draft, it is easier to mark ready than to un-request review

**Agent validation:**
- For Feature PRs: launch security-auditor + test-generator + code-quality-reviewer agents
- For Bug fix PRs: focus on test-generator only
- For Refactor PRs: focus on code-quality-reviewer only
- For Quick PRs: skip agent validation entirely



---

## References (9)

### CI Integration Patterns


# CI Integration Patterns

## Auto PR Linking (CC 2.1.27+)

Sessions created via `gh pr create` are automatically linked to the PR. Resume linked sessions with:

```bash
claude --from-pr 123          # Resume session linked to PR #123
claude --from-pr https://github.com/org/repo/pull/123
```

PR context (diff, comments, review status) is available when resuming.

## Task Metrics (CC 2.1.30+)

Agent tool results include `token_count`, `tool_uses`, and `duration_ms`. Report validation efficiency in PR comments:

```markdown
## Pre-PR Validation Metrics
| Agent | Tokens | Tools | Duration |
|-------|--------|-------|----------|
| security-auditor | 520 | 10 | 15s |
| test-generator | 380 | 6 | 12s |
| code-quality-reviewer | 450 | 8 | 10s |

**Total:** 1,350 tokens in 37s
```

## Session Resume Hints (CC 2.1.31+)

Before ending PR creation sessions, store context for future sessions:

```bash
/ork:remember PR #123 created: [brief description], pending review from [team]
```

## Post-Creation Verification

After `gh pr create`, always verify:

```bash
PR_URL=$(gh pr view --json url -q .url)
echo "PR created: $PR_URL"

# Check CI status (wait a moment for checks to start)
gh pr checks
```

## Issue Auto-Close

Using `Closes #N` in the PR body auto-closes the issue when the PR merges to the default branch. This is handled by GitHub, not CI, but only works when merging to `main`/`dev` (the repository's default branch).


### Coderabbit Cli Prereview

# CodeRabbit CLI pre-review: find defects before the PR spends its review allowance

Phase 3c of create-pr. Runs the CodeRabbit CLI against the branch diff before
`gh pr create`. Advisory only: every failure path prints one line and continues.

## Why the PR review allowance is the scarce resource

CodeRabbit PR reviews on the Essentials plan are rate limited per GitHub identity,
with an adaptive refill that shrinks as usage grows: 0 to 29 reviews in the trailing
7 days refills 5 reviews per hour, stepping down to 1 per hour at 60 or more
(docs.coderabbit.ai/management/rate-limits). The limit follows the identity, not the
repo, so every repo the operator pushes to draws from the same bucket.

Measured 2026-09-14: the operator's identity sat at the 1 per hour floor, and 85% of
PR review events in the prior 30 days were rate limited. Every push to an open PR is
a new review event, so a PR that needs three fix pushes spends three, and a
rate-limited review never arrives, which the harvest phase then reads as a clean zero.

The CLI draws on a separate allowance. Reviewing the branch locally before the PR
exists finds defects while fixing them is free, so the PR opens cleaner and needs
fewer follow-up pushes.

## The command

Flags verified against CodeRabbit CLI 0.7.6 (`coderabbit review --help`,
2026-09-14). The binary installs as `coderabbit`; `cr` is a common symlink.

```bash
# `cr` is also the name of unrelated tools (helm chart-releaser, for one), so
# accept it only when its help text identifies CodeRabbit.
CR_BIN=""
if command -v coderabbit >/dev/null 2>&1; then
  CR_BIN=coderabbit
elif command -v cr >/dev/null 2>&1 && grep -q CodeRabbit <<<"$(cr --help 2>&1)"; then
  CR_BIN=cr
fi

# mktemp, never a fixed name: a predictable path in a shared /tmp can be
# pre-created or symlinked by another local user.
CR_OUT=$(mktemp "${TMPDIR:-/tmp}/cr-prereview.XXXXXX")
if [ -z "$CR_BIN" ]; then
  echo "CodeRabbit CLI pre-review skipped: CLI not on PATH"
elif perl -e 'alarm shift; exec @ARGV' 600 \
       "$CR_BIN" review --agent --base "origin/$BASE" </dev/null >"$CR_OUT" 2>&1; then
  echo "CodeRabbit CLI pre-review finished: $CR_OUT"
else
  echo "CodeRabbit CLI pre-review skipped: exit $? (142 means the 10 minute alarm fired), see $CR_OUT"
fi
```

| Flag | Why |
|---|---|
| `--agent` | Structured findings meant for an agent to read, instead of the plain-text default |
| `--base "origin/$BASE"` | Compare the branch against the remote base; a worktree's local base branch is often stale or absent |
| `</dev/null` | A signed-out CLI cannot sit on an interactive prompt |

### `--use-credits` and what actually controls spend

Never pass `--use-credits`, but do not read its absence as a spend guarantee. CLI reviews
follow the same billing path as PR reviews: the assigned user's plan allowance first, then
the organization's usage-based add-on. What happens past the included limit depends on the
add-on mode an admin set (docs.coderabbit.ai/management/usage-based-addon, read 2026-09-14):

| Mode | Past the included limit | Does omitting `--use-credits` avoid spend? |
|---|---|---|
| Automatic | "CodeRabbit continues every eligible review automatically", and non-trial overages are billed | No. The review runs and bills without the flag |
| On demand | "Reviews pause at the limit"; each paid review needs explicit authorization, which for the CLI is `coderabbit review --use-credits` | Yes |
| Off | "Reviews stop when the included limit is reached" | Yes |

So the flag is the authorization step only in On demand mode. If the org runs Automatic,
this phase can spend credits no matter how the command is written; the control is the
add-on mode in the CodeRabbit dashboard, not this snippet.

### Why `perl` and not `timeout`

macOS ships no `timeout` binary (`gtimeout` exists only with coreutils installed).
`perl` is present on macOS and on mainstream Linux images. `alarm` survives `exec`,
so the CLI process itself receives SIGALRM and exits 142 (128 + 14). Only the exec'd
process is signalled; a helper the CLI spawned can outlive it.

## How it degrades

| Condition | What happens |
|---|---|
| Neither `coderabbit` nor a CodeRabbit `cr` on PATH | One-line skip note, continue to Phase 4 |
| Not signed in | Non-zero exit or the alarm fires; one-line note, continue |
| Review runs past 10 minutes | Alarm fires, exit 142; one-line note, continue |
| Sandboxed shell blocks the CLI's storage or websocket (`coderabbit doctor` reports it) | Non-zero exit; one-line note, continue |

The signed-out exit code was not measured. The alarm bounds that case either way.

## Acting on findings

1. Read `$CR_OUT`. Treat every finding as a claim, not a verdict.
2. Fix a finding only when it is a clear defect in lines this branch changed and
   you can confirm the fix locally (a test, a lint, or reading the code path).
   Skip style nits, speculative edge cases, and anything outside the diff.
3. Commit the fixes, then re-run Phase 2 local validation.
4. Do not re-run the CLI review. One pass per PR: a second run spends CLI
   allowance, and a fix-then-review loop has no natural stopping point.
5. Record the outcome in the PR body's Test Plan, for example
   `CodeRabbit CLI pre-review: 4 findings, 2 fixed, 2 dismissed as nits`, or the
   one-line skip reason.

## Relationship to the harvest phase

The pre-review is local and happens before the PR exists. The harvest reads
CodeRabbit's threads on the PR after CI is green. The pre-review does not replace
the harvest: findings fixed locally simply never become threads, and the harvest
still runs, because the PR review sees the whole diff with repo configuration the
local run may not apply.


### Coderabbit Harvest

# CodeRabbit harvest (after checks green, before merge or auto-merge)

CodeRabbit reviews expire unread: on Yonatan-HQ/platform (last 80 PRs, measured 2026-09-02)
23 of the 24 PRs it reviewed merged with every thread still open. This phase reads the threads
once, refutes each, and closes every one with a stated outcome. Run it after CI is green and
before `gh pr merge` or arming `--auto`. Skip only when the repo has no `.coderabbit.yaml` or
the PR is still a draft. Defects fixed in Phase 3c never become threads; the harvest still runs.

```bash
H="${CLAUDE_SKILL_DIR}/scripts/coderabbit-harvest.sh"
PR_NUMBER=$(gh pr view --json number -q .number)
bash "$H" "$PR_NUMBER" --unresolved > /tmp/cr-threads.json   # ONE GraphQL call, coderabbitai only
jq length /tmp/cr-threads.json                                  # 0 → see below, then merge
```

A zero here is ambiguous: CodeRabbit read the diff and found nothing, or CodeRabbit never
saw the PR. Both print `0`, and the phase treats both as clean, so a repo the app cannot
reach passes this gate on every PR forever. Measured on this repo 2026-09-03: a valid
`.coderabbit.yaml` had been on `main` since 2026-08-25 while CodeRabbit had posted zero
comments in the repo's entire history and ignored an explicit `@coderabbitai review`, so
the app is either uninstalled on the `yonatangross` account or disabled account-side.
Note what that verdict does NOT say: absence never proves absence from a session, only the
settings page separates those two. The first time a repo's harvest returns zero,
disambiguate with
`Read("references/coderabbit-zero-reviews.md")`:
it carries the two-endpoint query, the positive control that keeps a zero honest, and the
operator steps for the installation grant. Editing `.coderabbit.yaml` cannot fix an app
that was never installed.

For each unresolved thread, spawn a refuter in ONE message: an isolated `Agent` (no
`team_name`), `subagent_type="ork:code-quality-reviewer"` (`ork:security-auditor` for a
security category). Give it `path`, `line`, `title`, `body`, and the diff slice
`git diff origin/$BASE...HEAD -- <path>`; instruct it to REFUTE the finding against that diff
and return `{"refuted": true|false, "reason": "<one sentence>"}`, defaulting to `refuted: true`
when the failure scenario cannot be reproduced. Refuters never edit files.

| Outcome | Action on the branch | Reply on the thread |
|---|---|---|
| Survived (`refuted: false`) | Fix in the same branch, commit, push | `Fixed in <sha>: <one line>` |
| Refuted (`refuted: true`) | Nothing | `Dismissed: <the one-sentence reason>` |

```bash
bash "$H" "$PR_NUMBER" --reply "$THREAD_ID" "Fixed in $(git rev-parse --short HEAD): <what changed>"
bash "$H" "$PR_NUMBER" --resolve "$THREAD_ID"       # every thread, fixed or dismissed
bash "$H" "$PR_NUMBER" --unresolved | jq length     # must print 0 before proceeding
```

A fix push re-runs CI, so re-verify green afterwards. CodeRabbit may post new threads on the
fix commit; give those exactly one more pass, then dismiss the rest with `Deferred to #<issue>`
and a filed issue, so the phase terminates instead of looping.


### Coderabbit Zero Reviews

# CodeRabbit harvest returns zero: reviewed clean, or never reviewed?

`coderabbit-harvest.sh --unresolved` printing `[]` has two causes that look identical
from the harvest phase: CodeRabbit read the diff and found nothing, or CodeRabbit never
saw the PR at all. The harvest phase treats both as "continue to merge", so a repo the
app cannot reach passes the quality bar on every PR forever.

Measured on yonatangross/orchestkit, 2026-09-03: `.coderabbit.yaml` had been on `main`
since 2026-08-25 (#3746), valid, with `auto_review.enabled: true`. Every PR review
comment in the repo's entire history came from `github-advanced-security[bot]` (56) or
`Copilot` (9). CodeRabbit had posted zero, ever, and an explicit `@coderabbitai review`
on an open PR went unanswered. The config was never the problem and editing it would
never have fixed anything: CodeRabbit is not reaching this repo at all, because the app
is either uninstalled on the account that owns it or disabled account-side. Which of the
two it is cannot be settled from a session, only from the settings page, and the ladder
below is built so that gap stays visible instead of getting rounded off.

The paragraph above is kept as history: it was true on 2026-09-03 and is no longer.

## Current state, 2026-09-14: installed and reviewing

CodeRabbit now reaches yonatangross/orchestkit. On 2026-09-14 `coderabbitai[bot]` posted a
full review on #4117 (submitted 13:13Z, 2 inline comments), and orchestkit is listed as a
connected public repository in the yonatangross CodeRabbit org. The installation gap
diagnosed above was closed account-side; nothing in `.coderabbit.yaml` changed to fix it.

With the app installed, the common cause of a zero on a single PR is no longer the grant.
It is the per-identity rate limit (docs.coderabbit.ai/management/rate-limits): a PR whose
review event arrives while the author's allowance is empty gets a `coderabbitai[bot]`
comment saying "Review limit reached" instead of a review, and the harvest then reads `[]`.
Check for that comment first. The ladder below still applies when a repo shows no
CodeRabbit activity at all.

## Disambiguate before you trust the zero

Run this once per repo. It is cheap and it separates "clean" from "unreachable".

```bash
R=owner/repo
# 1. Has CodeRabbit EVER commented here? Both endpoints, because they are different
#    surfaces: summaries land as issue comments, findings as PR review comments.
#    A single-endpoint read is structurally blind to half the answer.
#
#    NO `since=`. It bounds the window, and it filters on updated_at rather than
#    created_at, so a repo whose CodeRabbit activity predates the cutoff returns 0
#    and reads exactly like a repo that was never installed. If you must bound it
#    for rate-limit reasons, the answer you get is "none in that window", never
#    "none ever", and the table below only accepts the unbounded form.
gh api --paginate "/repos/$R/issues/comments?per_page=100" \
  --jq '.[] | select(.user.login=="coderabbitai[bot]") | (.issue_url|split("/")|last)' | sort -u | wc -l
gh api --paginate "/repos/$R/pulls/comments?per_page=100" \
  --jq '.[] | select(.user.login=="coderabbitai[bot]") | (.pull_request_url|split("/")|last)' | sort -u | wc -l

# 2. POSITIVE CONTROL. Same query against a repo where CodeRabbit is known to run.
#    Without it, a zero is indistinguishable from a wrong query: the bot login is
#    `coderabbitai[bot]`, and the bare `coderabbitai` form silently returns 0.

# 3. CORROBORATION ONLY, never proof on its own. GitHub creates a check-suite per
#    installed app that has the checks permission; an installed app without it emits
#    nothing, so a missing slug does not by itself mean "not installed" for an
#    arbitrary app. It does mean it for CodeRabbit, but only because that was
#    MEASURED: on Yonatan-HQ/platform, where CodeRabbit demonstrably reviews, the
#    `coderabbitai` slug is present on every PR head checked (#11024, #11057, #11058).
#    Run this against your control repo FIRST. If the slug is absent there too, this
#    probe is inert for your setup and step 1 is the only evidence you have.
#    Query check-SUITES, not check-RUNS: on those same platform PRs the check-runs
#    app list is `github-actions` alone, because CodeRabbit opens a suite and files
#    no runs in it.
SHA=$(gh pr view <n> --repo "$R" --json headRefOid --jq .headRefOid)
gh api "/repos/$R/commits/$SHA/check-suites?per_page=100" --jq '.check_suites[].app.slug' | sort -u

# 4. REQUIRED before you say "not installed". A lifetime zero is ALSO what an
#    installed-but-suppressed app produces: the repo can be disabled in the
#    CodeRabbit dashboard, paused with `@coderabbitai pause`, or excluded from
#    day one by an `ignore_usernames` covering the only author or a
#    `path_filters` matching everything. Steps 1 to 3 cannot separate those from
#    an absent installation, because all of them post nothing, ever.
#
#    Manual commands are documented to work independently of auto-review
#    configuration (docs.coderabbit.ai/guides/commands), so an installed app
#    answers a direct request even with `auto_review.enabled: false`. Post this
#    on an OPEN pr and wait:
#        @coderabbitai review
#    Any reply, including a refusal or a config dump from `@coderabbitai
#    configuration`, means INSTALLED, and the fault is settings, not the grant.
#    Silence, with steps 1 to 3 also at zero, is the strongest reading available
#    without the browser. It is still "not installed OR disabled account-side",
#    never "not installed" alone.
```

## Read the result

| Signal | Strength | Meaning | Fix |
|---|---|---|---|
| A `coderabbitai[bot]` comment on this PR saying "Review limit reached" | conclusive | Installed; this PR's review event was rate limited for the author's identity | Wait for the allowance to refill, then `@coderabbitai review`; push fewer times per PR |
| Zero comments unbounded on both endpoints, control non-zero, **and silence after an explicit `@coderabbitai review`** | strong, still not conclusive | Not installed for this owner account, **or** installed and disabled account-side | Settings page decides which; install or re-enable |
| Zero comments unbounded, but no explicit-trigger probe was run | **inconclusive** | Indistinguishable from a repo disabled in the dashboard or excluded since day one | Run step 4 before concluding |
| No `coderabbit` check-suite slug, **and** the control repo has one | corroborating | Agrees with the row above; cannot carry the diagnosis alone | Same |
| Zero comments but the query carried a `since=` | **none** | Bounded window, not "ever". Re-run unbounded before concluding anything | rerun step 1 |
| **Any** reply to `@coderabbitai review`, however unhelpful | conclusive | Installed. The fault is configuration or the dashboard, never the grant | `.coderabbit.yaml` or dashboard |
| Comments exist but stopped on a date | conclusive | Subscription lapsed, or the repo was dropped from the installation grant | Dashboard, below |
| Comments exist and are recent, this PR has none | conclusive | Real config exclusion: check `path_filters`, `ignore_title_keywords`, `ignore_usernames`, `base_branches`, `drafts` | `.coderabbit.yaml` |

Only the rows marked conclusive may end the investigation, and **no row reachable without
the browser is conclusive for "not installed"**. That asymmetry is the point: a reply
proves presence outright, while absence never fully proves absence, so the honest verdict
from a session is always "not installed or disabled account-side, settings page not read".
A valid config is not evidence of a running reviewer, and neither is a bounded zero, a
missing check-suite, nor an unbounded zero on its own.

The installation list itself is the one direct answer, and it is not reachable from a
session: `gh api /user/installations` returns 403 on a normal user token ("must
authenticate with an access token authorized to a GitHub App"). So the direct path is
https://github.com/settings/installations and https://app.coderabbit.ai, both browser-only.
When the browser is unavailable, steps 1 to 4 together are the strongest evidence you can
get, and the verdict should say so rather than implying the settings page was read.

## The leading hypothesis: installation is per owner account, not per repo

The GitHub App is installed on an organization or on a user account, and each installation
carries its own repository grant. An org installation does not cover a personal repo owned
by a member of that org, even for the same person and the same subscription. That fits
orchestkit: `Yonatan-HQ/platform` (org-owned) had CodeRabbit on 238 of 311 non-bot PRs
since 2026-08-20, while `yonatangross/orchestkit` (user-owned, public) had zero over the
same window with the identical query.

It is a hypothesis, not a reading. The competing explanation, that the app is installed on
the personal account and the repo is switched off in the CodeRabbit dashboard, produces the
same four zeros. The two are separated at the settings page and nowhere else, and steps 1
and 2 below cover both: whoever opens that page will see immediately which one it was.

Operator action, not automatable from a session (it is a web OAuth grant):

1. https://github.com/settings/installations, find CodeRabbit.
2. If absent, install it on the personal account from https://github.com/apps/coderabbitai.
3. If present, open Configure and add the repo to "Only select repositories", or switch to
   all repositories.
4. Confirm in the CodeRabbit dashboard (https://app.coderabbit.ai) that the repo is listed
   and enabled under the right org or account.

Then prove it with a PR, not with the settings page: open one and wait for
`gh pr view <n> --json reviews,comments` to show a `coderabbitai[bot]` entry. A green
config and a checked box are not a posted review.

## Public repositories are free

CodeRabbit gives free reviews for public repositories to accounts signed up through GitHub
(coderabbit.ai/pricing, read 2026-09-03). A public repo does not need a paid seat and does
not consume one, so "we are not paying for that repo" is never a reason to leave the app
uninstalled on it.


### Multi-Commit PR Guidance


# Multi-Commit PR Guidance

## When PRs Have Multiple Commits

Most PRs should have a clean commit history. The PR title summarizes the overall change; individual commits tell the story of how you got there.

## Gathering Context

Before writing the PR body, review the full commit history:

```bash
BRANCH=$(git branch --show-current)
ISSUE=$(echo "$BRANCH" | grep -oE '[0-9]+' | head -1)

# See all commits on this branch
git log --oneline dev..HEAD

# See overall diff stats
git diff dev...HEAD --stat

# See full diff for PR body writing
git diff dev...HEAD
```

## PR Body for Multi-Commit PRs

When the PR has multiple commits, the Changes section should group by logical area, not list each commit:

```markdown
## Summary
Implement user authentication with JWT tokens.

## Changes

### Backend
- Add JWT token generation and validation
- Create login/logout API endpoints
- Add auth middleware for protected routes

### Frontend
- Create login form component
- Add auth context provider
- Implement protected route wrapper

### Infrastructure
- Add JWT_SECRET to environment config
- Update Docker compose with auth service

## Commit History
- `abc1234` feat: add JWT auth backend
- `def5678` feat: add login form frontend
- `ghi9012` chore: configure JWT environment
- `jkl3456` test: add auth integration tests
```

## Squash vs Merge

- **Squash merge**: All commits become one. PR title becomes the commit message. Best for feature branches.
- **Merge commit**: Preserves individual commits. Best when commit history is clean and tells a story.
- **Rebase merge**: Linear history. Best for small, single-commit PRs.

Let the repository's merge strategy setting guide this, do not override per-PR unless there is a reason.


### Parallel Validation Agents


# Parallel Validation Agents

## Overview

For Feature and Bug fix PRs, launch validation agents in parallel BEFORE creating the PR. All three agents run in ONE message using `run_in_background=True`.

## Agent Configuration

### Security Auditor

```python
Agent(
  subagent_type="ork:security-auditor",
  prompt="""Security audit for PR changes:
  1. Check for secrets/credentials in diff
  2. Dependency vulnerabilities (npm audit/pip-audit)
  3. OWASP Top 10 quick scan
  Return: {status: PASS/BLOCK, issues: [...]}

  Scope: ONLY read files directly relevant to the PR diff. Do NOT explore the entire codebase.

  SUMMARY: End with: "RESULT: [PASS|WARN|BLOCK] - [N] issues: [brief list or 'clean']"
  """,
  run_in_background=True,
  max_turns=25
)
```

### Test Generator

```python
Agent(
  subagent_type="ork:test-generator",
  prompt="""Test coverage verification:
  1. Run test suite with coverage
  2. Identify untested code in changed files
  Return: {coverage: N%, passed: N/N, gaps: [...]}

  Scope: ONLY read files directly relevant to the PR diff. Do NOT explore the entire codebase.

  SUMMARY: End with: "RESULT: [N]% coverage, [passed]/[total] tests - [status]"
  """,
  run_in_background=True,
  max_turns=25
)
```

### Code Quality Reviewer

```python
Agent(
  subagent_type="ork:code-quality-reviewer",
  prompt="""Code quality check:
  1. Run linting (ruff/eslint)
  2. Type checking (mypy/tsc)
  3. Check for anti-patterns
  Return: {lint_errors: N, type_errors: N, issues: [...]}

  Scope: ONLY read files directly relevant to the PR diff. Do NOT explore the entire codebase.

  SUMMARY: End with: "RESULT: [PASS|WARN|FAIL] - [N] lint, [M] type errors"
  """,
  run_in_background=True,
  max_turns=25
)
```

## When to Use Agents

| PR Type | Agents |
|---------|--------|
| Feature | All 3 (security + tests + quality) |
| Bug fix | test-generator only |
| Refactor | code-quality-reviewer only |
| Quick | None, skip agent validation |

## After Agent Results

Wait for all agents to complete, then run local validation (lint + test) to confirm. Agent results inform the PR body's Test Plan section.


### PR Body Templates


# PR Body Templates

## Standard Template

For most PRs (features, bug fixes, refactors):

```markdown
## Summary
[1-2 sentence description of what this PR does and why]

## Changes
- [Change 1]
- [Change 2]

## Type
- [ ] Feature
- [ ] Bug fix
- [ ] Refactor
- [ ] Docs
- [ ] Test
- [ ] Chore

## Breaking Changes
- [ ] None
- [ ] Yes: [describe migration steps]

## Related Issues
- Closes #ISSUE

## Test Plan
- [x] Unit tests pass
- [x] Lint/type checks pass
- [ ] Manual testing: [describe]

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

## Minimal Template

For small fixes and chores (under 50 lines changed):

```markdown
## Summary
Fix typo in error message

## Test Plan
- [x] Unit tests pass

Closes #123

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

## Feature Template

For new features with UI, infrastructure, or deployment implications:

```markdown
## Summary
Add user profile page with avatar upload

## Changes
- Create ProfilePage component
- Add profile API endpoint
- Implement avatar upload with S3
- Add unit and integration tests

## Screenshots
[If applicable — paste screenshots of UI changes]

## Test Plan
- [x] Unit tests: 15 new tests
- [x] Integration tests: 3 new tests
- [x] Manual testing: Verified upload works
- [x] Accessibility: Keyboard navigation works

## Deployment Notes
- Requires S3 bucket configuration
- Run migration: `alembic upgrade head`

Closes #456

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

## HEREDOC Pattern for `gh pr create`

Always use HEREDOC to avoid shell escaping issues:

```bash
gh pr create --base dev \
  --title "feat(#123): add user profile page" \
  --body "$(cat <<'EOF'
## Summary
Add user profile page with avatar upload and S3 integration.

## Changes
- Create ProfilePage component
- Add profile API endpoint
- Implement avatar upload with S3

## Test Plan
- [x] Unit tests pass (15 new)
- [x] Lint/type checks clean

Closes #123

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


### Pr Playground

# Phase 4b: Generate PR Playground (REQUIRED, CI blocks without it)

Generate an interactive HTML playground visualizing the PR's changes. CI validates `docs/{branch-name}/*.html` exists.

> **Requires** the `playground` plugin (external): `/plugin marketplace add anthropics/claude-plugins-official && /plugin install playground`

**First classify the archetype**: a feature PR must not ship as a flat dashboard.
`Read("${CLAUDE_PLUGIN_ROOT}/shared/rules/playground-visual-standard.md")` and apply its §0 routing rule:

- **Visual PR** (adds/changes a user-facing feature, flow, or a prioritization/decision surface) →
  **USER-STORY PLAYER** or **DECISION BOARD**. Build to the standard: adapt the matching exemplar at
  `${CLAUDE_PLUGIN_ROOT}/shared/assets/playground-exemplars/` (`user-story-player.template.html`
  or `decision-board.template.html`), and bring full design firepower (the `frontend-design` skill /
  the `ork:frontend-ui-developer` agent). When delegating to `playground:playground`, brief it with the
  **archetype + persona + tokens**: never hand it a pre-built HTML blob.
- **Non-visual PR** (infra/CI/refactor/config/docs) → **DASHBOARD**, the default summary below is fine.

```python
BRANCH=$(git branch --show-current)
BRANCH_DIR = BRANCH.replace("/", "--")  # feat/foo → feat--foo

# Invoke the playground skill with a summary of the PR changes.
# For a VISUAL PR, set archetype/persona/exemplar per playground-visual-standard.md instead of this default.
Skill("playground:playground", args=f"""
  {PR_TITLE} — visualize the key changes in this PR.
  Archetype: <user-story-player | decision-board | dashboard> per playground-visual-standard.md §0.
  For visual archetypes: follow that standard's tokens/glass/motion and adapt the matching exemplar.
  Show: architecture/data flow, before/after, key components changed; presets for the main change areas.
  Dark glass theme, OrchestKit brand accents.
""")

# The playground skill writes to a temp path — move it to the correct location
# Ensure file lands at: docs/{branch-dir}/<name>.html
Bash(f"mkdir -p docs/{BRANCH_DIR}")
Bash(f"mv /tmp/*.html docs/{BRANCH_DIR}/playground.html 2>/dev/null || true")

# Force-add (docs/feat--*/ is gitignored by design)
Bash(f"git add -f docs/{BRANCH_DIR}/")
Bash(f'git commit -m "docs: add PR playground for {BRANCH}"')
Bash(f"git push origin {BRANCH}")
```

Resolve the head SHA first, and pin the link to it:

```bash
Bash("git rev-parse HEAD")   # -> {HEAD_SHA}
```

Add a "Live Preview" section to the PR body:

```markdown
## Live Preview

**[Open Interactive Playground](https://github.com/{OWNER}/{REPO}/blob/{HEAD_SHA}/docs/{BRANCH_DIR}/playground.html)**
```

> **First-party link only.** Do not wrap the URL in `htmlpreview.github.io` or any
> other render proxy. A proxy serves the repo's HTML from an origin the project does
> not control, with none of its CSP applied. The plain blob link shows source rather
> than a rendered page; that is the accepted trade-off for not handing a third party
> the content. To get a *rendered* first-party page, publish the playground to the
> Lab instead: add `docs/site/lab-manifest/<slug>.json` (one fragment per entry) and
> run `npm run build`, which serves it under the site's own `/lab` CSP. The
> `lab-data.ts` aggregate is generated at build, dev and test time and is not
> committed, so there is no conflict to resolve on it: publish a playground with
> one fragment under `docs/site/lab-manifest/` and rebuild.

> **Pin the SHA, never the branch.** GitHub deletes the head branch on merge, so a
> `blob/{BRANCH}/` URL returns 404 the moment the PR lands. That silently broke the
> playground link on every merged PR through #3147. A commit SHA stays reachable
> indefinitely because GitHub retains `refs/pull/<N>/head`, so the same URL works
> during review and after merge. Verified: branch form 404, SHA form 200, on a PR
> whose branch was already deleted.

> **Why required:** CI Stage 1d (`playground-check`) blocks merge if `docs/{branch-dir}/*.html` is missing. Bot PRs (dependabot, release-please) are exempt.


### Stacked Pr

# Stacked PRs

Loaded on demand from the "Stacked on another open PR" section of SKILL.md.

A stack is a chain of small PRs: the bottom one targets the trunk, each layer above targets the
branch below it. It exists so a 1,500 line feature can be reviewed as four readable pieces. The
cost is paid at landing time, and that is where every recipe below applies.

## Resolve the trunk, never type it

```bash
TRUNK=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')
# empty? the symref is unset: git remote set-head origin -a
```

`main` is a guess. Repos run on `dev` and `develop`, and `main` often still exists beside them
as a release branch, so a hardcoded name silently targets the wrong one.

## Opening a layer

```bash
PARENT=50
PARENT_BRANCH=$(gh pr view "$PARENT" --json headRefName -q .headRefName)
PARENT_TIP=$(git rev-parse "origin/$PARENT_BRANCH")   # record it, you need it later

git fetch origin
git rebase "origin/$PARENT_BRANCH"
git push -u origin "$(git branch --show-current)"
gh pr create --base "$PARENT_BRANCH" \
  --title "feat(#$ISSUE): <layer> [2/3]" \
  --body "Stacked on #$PARENT (parent tip $PARENT_TIP). Do not merge into the parent branch."
```

Number the titles `[1/3]`, `[2/3]`, `[3/3]` so review order is visible. Keep each layer under
400 lines. Do not go deeper than 4 layers: CI runs multiply per layer, and a measured 3 layer
stack cost 24 runs.

## After the parent is squash-merged

The parent lands on the trunk as ONE new commit. The layer above still carries the parent's
original commits, so `git rebase "$TRUNK"` replays content the trunk already has and conflicts
on every commit. Use the onto form, which drops them:

```bash
git fetch origin
OLD_TIP=$(git rev-parse "origin/$BRANCH")   # the lease, read before rewriting
git rebase --onto "origin/$TRUNK" "$PARENT_TIP" "$BRANCH"
git push --force-with-lease="$BRANCH:$OLD_TIP" origin "$BRANCH"
```

If the parent tip was not recorded, it is still on the merged PR:
`gh pr view "$PARENT" --json headRefOid -q .headRefOid`. That field survives the branch delete.

The upstream argument is always the parent tip the branch actually contains. A third layer
re-parents onto layer 2's NEW tip first, then repeats this once layer 2 lands.

`--force-with-lease` with no value checks the local remote-tracking ref, which a background
fetch can update behind you. Name the branch and the sha.

## Traps, all four measured on a real 3 layer stack

1. **No ordinary auto-merge on a native stack layer.** GitHub refuses it; a layer lands through
   the stack merge API (`gh stack merge`), or the stack is hand chained and each layer lands
   normally once its base is the trunk.
2. **`gh stack submit --auto` opens every layer as a DRAFT.** A stack with a draft in it cannot
   merge, and nothing says so. Use `--auto --open`, or flip layers ready top down before
   landing.
3. **`gh stack init` reads the LOCAL `origin/HEAD`, which goes stale.** Pass the trunk
   explicitly: `gh stack init -b "$TRUNK"`. A stale symref silently builds the stack on the
   wrong branch.
4. **When the parent merges, the layer above never receives `edited`.** It receives
   `automatic_base_change_succeeded` and `head_ref_force_pushed` together as a pair, about two
   seconds after the merge. Automation keyed on `edited` never fires, and automation that waits
   for a second event after seeing the first is waiting for something that already arrived.

## Landing

Bottom up, always. Merging a layer into its parent branch instead of the trunk puts unreviewed
lines on the trunk when the parent lands, and a feature branch has no protection to stop it.
Never merge the trunk into a layer either: a merge commit inside a layer breaks the linear
history a stack needs to merge at all.
