From 11f466825d8723a7f63b901facc60a5a3b86cee2 Mon Sep 17 00:00:00 2001 From: Trevin Chow Date: Tue, 14 Apr 2026 15:12:07 -0700 Subject: [PATCH 01/14] feat(ce-pr-description): extract PR description writing into a focused skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New skill owns the value-first description writing logic that was previously inline in git-commit-push-pr. Input contract is two-shape: pr: for existing PRs (used by refresh mode), or range:.. for pre-PR generation (used by new PR creation and by ce-pr-stack per layer). Output is structured {title, body}; caller decides whether to apply via gh pr edit. No interactive prompts, no auto-apply, no branch coupling. The skill is a pure capability — git-commit-push-pr wraps it with confirmation prompts and evidence-capture for single-PR interactive flows; ce-pr-stack (via git-commit-push-pr's stack-aware routing, coming next) will call it per layer without the interactive scaffolding. Refactor preserves git-commit-push-pr's user-facing behavior: - Full flow: commit + push + create PR, description generated via ce-pr-description with range:.. and passed to gh pr create in a single call (no transient placeholder on GitHub) - Refresh mode (DU-1/DU-2/DU-3): interactive scaffolding stays; DU-3 delegates description generation to ce-pr-description with pr:, then presents the compare-and-confirm as before Naming: ce-pr-description, not git-pr-description. PR is a GitHub artifact; the ce- prefix matches the future convention for plugin skills. git-commit-push-pr will rename later. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../skills/ce-pr-description/SKILL.md | 284 ++++++++++++++++++ .../skills/git-commit-push-pr/SKILL.md | 210 +++---------- 2 files changed, 318 insertions(+), 176 deletions(-) create mode 100644 plugins/compound-engineering/skills/ce-pr-description/SKILL.md diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md new file mode 100644 index 000000000..e5e9af3d5 --- /dev/null +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -0,0 +1,284 @@ +--- +name: ce-pr-description +description: "Generate a value-first PR title and body for a GitHub pull request or arbitrary diff range, returning structured {title, body} output. Accepts either pr: (existing PR) or range:.. (pre-PR or dry-run), plus an optional focus hint. Used by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions); occasionally invoked directly when only a description rewrite is wanted. Does NOT apply the description — the caller decides whether and when to edit." +--- + +# CE PR Description + +Generate a conventional-commit-style title and a value-first body for a GitHub pull request from either an existing PR (`pr:`) or a raw diff range (`range:..`). Returns structured `{title, body}` for the caller to apply — this skill never invokes `gh pr edit` or `gh pr create`, and never prompts for interactive confirmation. + +Why a separate skill: several callers need the same writing logic without the single-PR interactive scaffolding that lives in `git-commit-push-pr`. `ce-pr-stack`'s splitting workflow runs this once per layer as a batch; `git-commit-push-pr` runs it inside its full-flow and refresh-mode paths. Extracting keeps one source of truth for the writing principles. + +**Naming rationale:** `ce-pr-description`, not `git-pr-description`. Stacking and PR creation are GitHub features; the "PR" in the name refers to the GitHub artifact. Using the `ce-` prefix matches the future convention for plugin skills; sibling `git-*` skills will rename to `ce-*` later, and this skill starts there directly. + +--- + +## Inputs + +Callers pass one of the two input forms below, plus an optional focus hint. If invoked directly by the user with no explicit form, infer from context (an existing open PR on the current branch -> `pr:`; a branch with no PR yet -> `range:..HEAD`). + +- **`pr: `** -- generate description for an existing PR. The skill reads title, body, and commit list via `gh pr view`, and derives the diff from the PR's commit range. +- **`range: ..`** -- generate description for an arbitrary range without requiring an existing PR. Useful before a PR is created, or as a dry-run for a branch being prepared for stack submission. +- **`focus: `** (optional) -- a user-provided steering note such as "include the benchmarking results" or "emphasize the migration safety story". Incorporate alongside the diff-derived narrative; do not let focus override the value-first principles. + +## Output + +Return a structured result with two fields: + +- **`title`** -- conventional-commit format: `type: description` or `type(scope): description`. Under 72 characters. Choose `type` based on intent (feat/fix/refactor/docs/chore/perf/test), not file type. Pick the narrowest useful `scope` (skill or agent name, CLI area, or shared label); omit when no single label adds clarity. +- **`body`** -- markdown following the writing principles below. + +The caller decides whether to apply via `gh pr edit`, `gh pr create`, or discard. This skill does NOT call those commands itself. + +--- + +## What this skill does not do + +- No interactive confirmation prompts. If the diff is ambiguous about something important (e.g., the focus hint conflicts with the actual changes), surface the ambiguity in the returned output or raise it to the caller — do not prompt the user directly. +- No branch checkout or assumption that HEAD is the target branch. Work from the input (`pr:` or `range:`) only. +- No compare-and-confirm narrative ("here's what changed since the last version"). The description describes the end state; the caller owns any compare-and-confirm framing. +- No auto-apply via `gh pr edit` or `gh pr create`. Return the output and stop. + +Interactive scaffolding (confirmation prompts, compare-and-confirm, apply step) is the caller's responsibility. + +--- + +## Step 1: Resolve the diff and commit list + +### If input is `pr: ` + +Fetch PR metadata and commit list: + +```bash +gh pr view --json number,state,title,body,baseRefName,headRefName,headRepositoryOwner,headRepository,commits,url +``` + +If the returned `state` is not `OPEN`, report "PR is (not open); cannot regenerate description" and exit gracefully without output. Callers expecting `{title, body}` must handle this empty case. + +Resolve the base remote and base branch from the PR metadata. Fall back to `origin` as the remote when match detection is ambiguous. Derive the diff range as `/...`. + +Verify the base remote-tracking ref exists; fetch if needed: + +```bash +git rev-parse --verify / 2>/dev/null || git fetch --no-tags +``` + +Gather merge base, commit list, and full diff: + +```bash +MERGE_BASE=$(git merge-base / ) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE.. && echo '=== DIFF ===' && git diff $MERGE_BASE... +``` + +Also capture the existing PR body for evidence preservation in Step 3. + +### If input is `range: ..` + +Validate both endpoints resolve: + +```bash +git rev-parse --verify 2>/dev/null && git rev-parse --verify 2>/dev/null +``` + +If either fails, report "Invalid range: .. -- does not resolve" and exit gracefully without output. + +Gather merge base, commit list, and full diff: + +```bash +MERGE_BASE=$(git merge-base ) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE.. && echo '=== DIFF ===' && git diff $MERGE_BASE... +``` + +If the commit list is empty, report "No commits between and " and exit gracefully. + +--- + +## Step 2: Classify commits before writing + +Scan the commit list and classify each commit: + +- **Feature commits** -- implement the PR's purpose (new functionality, intentional refactors, design changes). These drive the description. +- **Fix-up commits** -- iteration work (code review fixes, lint fixes, test fixes, rebase resolutions, style cleanups). Invisible to the reader. + +When sizing the description, mentally subtract fix-up commits: a branch with 12 commits but 9 fix-ups is a 3-commit PR. + +--- + +## Step 3: Decide on evidence + +Decide whether evidence capture is possible from the full branch diff. + +**Evidence is possible** when the diff changes observable behavior demonstrable from the workspace: UI, CLI output, API behavior with runnable code, generated artifacts, or workflow output. + +**Evidence is not possible** for: +- Docs-only, markdown-only, changelog-only, release metadata, CI/config-only, test-only, or pure internal refactors +- Behavior requiring unavailable credentials, paid/cloud services, bot tokens, deploy-only infrastructure, or hardware not provided + +**This skill does NOT prompt the user** to capture evidence. The decision logic is: + +1. **Input was `pr:` and the existing body contains a `## Demo` or `## Screenshots` section with image embeds:** preserve it verbatim unless the `focus:` hint asks to refresh or remove it. Include the preserved block in the returned body. +2. **Otherwise:** omit the evidence section entirely. If the caller wants to capture evidence, the caller is responsible for invoking `ce-demo-reel` separately and splicing the result in, or for asking this skill to regenerate with an updated focus hint after capture. + +Do not label test output as "Demo" or "Screenshots". Place any preserved evidence block before the Compound Engineering badge. + +--- + +## Step 4: Frame the narrative before sizing + +Articulate the PR's narrative frame: + +1. **Before**: What was broken, limited, or impossible? (One sentence.) +2. **After**: What's now possible or improved? (One sentence.) +3. **Scope rationale** (only if 2+ separable-looking concerns): Why do these ship together? (One sentence.) + +This frame becomes the opening. For small+simple PRs, the "after" sentence alone may be the entire description. + +--- + +## Step 5: Size the change + +Assess size (files, diff volume) and complexity (design decisions, trade-offs, cross-cutting concerns) to select description depth: + +| Change profile | Description approach | +|---|---| +| Small + simple (typo, config, dep bump) | 1-2 sentences, no headers. Under ~300 characters. | +| Small + non-trivial (bugfix, behavioral change) | Short narrative, ~3-5 sentences. No headers unless two distinct concerns. | +| Medium feature or refactor | Narrative frame (before/after/scope), then what changed and why. Call out design decisions. | +| Large or architecturally significant | Full narrative: problem context, approach (and why), key decisions, migration/rollback if relevant. | +| Performance improvement | Include before/after measurements if available. Markdown table works well. | + +When in doubt, shorter is better. Match description weight to change weight. + +--- + +## Step 6: Apply writing principles + +### Writing voice + +If the repo has documented style preferences in context, follow those. Otherwise: + +- Active voice. No em dashes or `--` substitutes; use periods, commas, colons, or parentheses. +- Vary sentence length. Never three similar-length sentences in a row. +- Do not make a claim and immediately explain it. Trust the reader. +- Plain English. Technical jargon fine; business jargon never. +- No filler: "it's worth noting", "importantly", "essentially", "in order to", "leverage", "utilize." +- Digits for numbers ("3 files"), not words ("three files"). + +### Writing principles + +- **Lead with value**: Open with what's now possible or fixed, not what was moved around. The subtler failure is leading with the mechanism ("Replace the hardcoded capture block with a tiered skill") instead of the outcome ("Evidence capture now works for CLI tools and libraries, not just web apps"). +- **No orphaned opening paragraphs**: If the description uses `##` headings anywhere, the opening must also be under a heading (e.g., `## Summary`). For short descriptions with no sections, a bare paragraph is fine. +- **Describe the net result, not the journey**: The description covers the end state, not how you got there. No iteration history, debugging steps, intermediate failures, or bugs found and fixed during development. This applies equally when regenerating for an existing PR: rewrite from the current state, not as a log of what changed since the last version. Exception: process details critical to understand a design choice. +- **When commits conflict, trust the final diff**: The commit list is supporting context, not the source of truth. If commits describe intermediate steps later revised or reverted, describe the end state from the full branch diff. +- **Explain the non-obvious**: If the diff is self-explanatory, don't narrate it. Spend space on things the diff doesn't show: why this approach, what was rejected, what the reviewer should watch. +- **Use structure when it earns its keep**: Headers, bullets, and tables aid comprehension, not mandatory template sections. +- **Markdown tables for data**: Before/after comparisons, performance numbers, or option trade-offs communicate well as tables. +- **No empty sections**: If a section doesn't apply, omit it. No "N/A" or "None." +- **Test plan — only when non-obvious**: Include when testing requires edge cases the reviewer wouldn't think of, hard-to-verify behavior, or specific setup. Omit when "run the tests" is the only useful guidance. When the branch adds test files, name them with what they cover. + +### Visual communication + +Include a visual aid only when the change is structurally complex enough that a reviewer would struggle to reconstruct the mental model from prose alone. + +**When to include:** + +| PR changes... | Visual aid | +|---|---| +| Architecture touching 3+ interacting components | Mermaid component or interaction diagram | +| Multi-step workflow or data flow with non-obvious sequencing | Mermaid flow diagram | +| 3+ behavioral modes, states, or variants | Markdown comparison table | +| Before/after performance or behavioral data | Markdown table | +| Data model changes with 3+ related entities | Mermaid ERD | + +**When to skip:** +- Sizing routes to "1-2 sentences" +- Prose already communicates clearly +- The diagram would just restate the diff visually +- Mechanical changes (renames, dep bumps, config, formatting) + +**Format:** +- **Mermaid** (default) for flows, interactions, dependencies. 5-10 nodes typical, up to 15 for genuinely complex changes. Use `TB` direction. Source should be readable as fallback. +- **ASCII diagrams** for annotated flows needing rich in-box content. 80-column max. +- **Markdown tables** for comparisons and decision matrices. +- Place inline at point of relevance, not in a separate section. +- Prose is authoritative when it conflicts with a visual. + +Verify generated diagrams against the change before including. + +### Numbering and references + +Never prefix list items with `#` in PR descriptions — GitHub interprets `#1`, `#2` as issue references and auto-links them. + +When referencing actual GitHub issues or PRs, use `org/repo#123` or the full URL. Never use bare `#123` unless verified. + +### Applying the focus hint + +If a `focus:` hint was provided, incorporate it alongside the diff-derived narrative. Treat focus as steering, not override: do not invent content the diff does not support, and do not suppress important content the diff demands simply because focus did not mention it. When focus and diff materially disagree (e.g., focus says "include benchmarking" but the diff has no benchmarks), note the conflict in a way the caller can see (leave a brief inline note or raise to the caller) rather than fabricating content. + +--- + +## Step 7: Compose the title + +Title format: `type: description` or `type(scope): description`. + +- **Type** is chosen by intent, not file extension. `feat` for new functionality, `fix` for a bug fix, `refactor` for a behavior-preserving change, `docs` for doc-only, `chore` for tooling/maintenance, `perf` for performance, `test` for test-only. +- **Scope** (optional) is the narrowest useful label: a skill/agent name, CLI area, or shared area. Omit when no single label adds clarity. +- **Description** is imperative, lowercase, under 72 characters total. No trailing period. +- If the repo has commit-title conventions visible in recent commits, match them. + +Breaking changes use `!` (e.g., `feat!: ...`) or document in the body with a `BREAKING CHANGE:` footer. + +--- + +## Step 8: Compose the body + +Assemble the body in this order: + +1. **Opening** -- the narrative frame from Step 4, at the depth chosen in Step 5. Under a heading (e.g., `## Summary`) if the description uses any `##` headings elsewhere; a bare paragraph otherwise. +2. **Body sections** -- only the sections that earn their keep for this change: what changed and why, design decisions, tables for data, visual aids when complexity warrants. Skip empty sections entirely. +3. **Test plan** -- only when non-obvious per the writing principles. Omit otherwise. +4. **Evidence block** -- only the preserved block from Step 3, if one exists. Do not fabricate or placeholder. +5. **Compound Engineering badge** -- append a badge footer separated by a `---` rule. Skip if the existing body (for `pr:` input) already contains the badge. + +**Badge:** + +```markdown +--- + +[![Compound Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin) +![HARNESS](https://img.shields.io/badge/MODEL_SLUG-COLOR?logo=LOGO&logoColor=white) +``` + +**Harness lookup:** + +| Harness | `LOGO` | `COLOR` | +|---------|--------|---------| +| Claude Code | `claude` | `D97757` | +| Codex | (omit logo param) | `000000` | +| Gemini CLI | `googlegemini` | `4285F4` | + +**Model slug:** Replace spaces with underscores. Append context window and thinking level in parentheses if known. Examples: `Opus_4.6_(1M,_Extended_Thinking)`, `Sonnet_4.6_(200K)`, `Gemini_3.1_Pro`. + +--- + +## Step 9: Return `{title, body}` + +Return the composed title and body to the caller. Do not call `gh pr edit`, `gh pr create`, or any other mutating command. Do not ask the user to confirm. The caller owns apply. + +Format the return as a clearly labeled block so the caller can extract cleanly: + +``` +=== TITLE === + + +=== BODY === +<body markdown> +``` + +If Step 1 exited gracefully (closed/merged PR, invalid range, empty commit list), return no title or body — just the reason string. + +--- + +## Cross-platform notes + +This skill does not ask questions directly. If the diff is ambiguous about something the caller should decide (e.g., focus conflicts with the actual changes, or evidence is technically capturable but the caller did not pre-stage it), surface the ambiguity in the returned output or a short note to the caller — do not invoke a platform question tool. + +Callers that need to ask the user are responsible for using their own platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini) before or after invoking this skill. diff --git a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md index 70e8f7b24..ebb501403 100644 --- a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md +++ b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md @@ -63,28 +63,28 @@ Use the current branch and existing PR check from context. If the current branch ### DU-3: Write and apply the updated description -Read the current PR description: +Read the current PR description to drive the compare-and-confirm step later: ```bash gh pr view --json body --jq '.body' ``` -Build the updated description: +**Generate the updated title and body** — load the `ce-pr-description` skill with `pr: <PR number from DU-2>`. If the user provided a focus (e.g., "include the benchmarking results"), pass it as `focus: <hint>`. The skill returns a `{title, body}` block without applying or prompting. -1. **Get the full branch diff** -- follow "Detect the base branch and remote" and "Gather the branch scope" in Step 6. Use the PR found in DU-2 for base branch detection. -2. **Classify commits** -- follow "Classify commits before writing" in Step 6. -3. **Decide on evidence** -- if the current description already has a `## Demo` or `## Screenshots` section with image embeds, preserve it unless the user's focus asks to refresh or remove it. If no evidence exists, follow "Evidence for PR descriptions" in Step 6. -4. **Rewrite the description from scratch** -- follow the writing principles in Step 6, driven by feature commits, final diff, and evidence decision. Do not layer changes onto the old description or document what changed since the last version. Write as if describing the PR for the first time. - - If the user provided a focus, incorporate it alongside the branch diff context. -5. **Compare and confirm** -- briefly explain what the new description covers differently from the old one. This helps the user decide whether to apply; the description itself does not narrate these differences. - - If the user provided a focus, confirm it was addressed. - - Ask the user to confirm before applying. +If `ce-pr-description` returns a "not open" or other graceful-exit message instead of a `{title, body}` pair, report that message and stop. -If confirmed, apply: +**Evidence decision:** `ce-pr-description` preserves any existing `## Demo` or `## Screenshots` block from the current body by default. If the user's focus asks to refresh or remove evidence, pass that intent via the `focus:` hint — the skill will honor it. If no evidence block exists and one would benefit the reader, invoke `ce-demo-reel` separately to capture, then re-invoke `ce-pr-description` with an updated focus that references the captured evidence. + +**Compare and confirm** — briefly explain what the new description covers differently from the old one. This helps the user decide whether to apply; the description itself does not narrate these differences. + +- If the user provided a focus, confirm it was addressed. +- Ask the user to confirm before applying. + +If confirmed, apply with the returned title and body: ```bash -gh pr edit --body "$(cat <<'EOF' -Updated description here +gh pr edit --title "<returned title>" --body "$(cat <<'EOF' +<returned body> EOF )" ``` @@ -157,13 +157,11 @@ If the PR check returned `state: OPEN`, note the URL -- continue to Step 4 and 5 git push -u origin HEAD ``` -### Step 6: Write the PR description +### Step 6: Generate the PR title and body The working-tree diff from Step 1 only shows uncommitted changes at invocation time. The PR description must cover **all commits** in the PR. -#### Detect the base branch and remote - -Resolve both the base branch and the remote (fork-based PRs may use a remote other than `origin`). Stop at the first that succeeds: +**Detect the base branch and remote.** Resolve both the base branch and the remote (fork-based PRs may use a remote other than `origin`). Stop at the first that succeeds: 1. **PR metadata** (if existing PR found in Step 3): ```bash @@ -184,188 +182,48 @@ Resolve both the base branch and the remote (fork-based PRs may use a remote oth If none resolve, ask the user to specify the target branch. -#### Gather the branch scope - -Verify the remote-tracking ref exists and fetch if needed: - -```bash -git rev-parse --verify <base-remote>/<base-branch> 2>/dev/null || git fetch --no-tags <base-remote> <base-branch> -``` - -Gather merge base, commit list, and full diff in a single call: - -```bash -MERGE_BASE=$(git merge-base <base-remote>/<base-branch> HEAD) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..HEAD && echo '=== DIFF ===' && git diff $MERGE_BASE...HEAD -``` - -Use the full branch diff and commit list as the basis for the description. - -#### Evidence for PR descriptions - -Decide whether evidence capture is possible from the full branch diff. - -**Evidence is possible** when the diff changes observable behavior demonstrable from the workspace: UI, CLI output, API behavior with runnable code, generated artifacts, or workflow output. - -**Evidence is not possible** for: -- Docs-only, markdown-only, changelog-only, release metadata, CI/config-only, test-only, or pure internal refactors -- Behavior requiring unavailable credentials, paid/cloud services, bot tokens, deploy-only infrastructure, or hardware not provided - -When not possible, skip without asking. When possible, ask: "This PR has observable behavior. Capture evidence for the PR description?" - -1. **Capture now** -- load the `ce-demo-reel` skill with a target description inferred from the branch diff. ce-demo-reel returns `Tier`, `Description`, and `URL`. Build a `## Demo` or `## Screenshots` section (browser-reel/terminal-recording/screenshot-reel use "Demo", static uses "Screenshots"). -2. **Use existing evidence** -- ask for the URL or markdown embed. -3. **Skip** -- no evidence section. - -If capture returns `Tier: skipped` or `URL: "none"`, do not add a placeholder. Summarize in the final report. - -Place evidence before the Compound Engineering badge. Do not label test output as "Demo" or "Screenshots". - -#### Classify commits before writing - -Scan the commit list and classify each commit: - -- **Feature commits** -- implement the PR's purpose (new functionality, intentional refactors, design changes). These drive the description. -- **Fix-up commits** -- iteration work (code review fixes, lint fixes, test fixes, rebase resolutions, style cleanups). Invisible to the reader. - -When sizing the description, mentally subtract fix-up commits: a branch with 12 commits but 9 fix-ups is a 3-commit PR. - -#### Frame the narrative before sizing - -Articulate the PR's narrative frame: - -1. **Before**: What was broken, limited, or impossible? (One sentence.) -2. **After**: What's now possible or improved? (One sentence.) -3. **Scope rationale** (only if 2+ separable-looking concerns): Why do these ship together? (One sentence.) +**Evidence decision (before delegation).** If the branch diff changes observable behavior (UI, CLI output, API behavior with runnable code, generated artifacts, workflow output) and evidence is not otherwise blocked (unavailable credentials, paid services, deploy-only infrastructure, hardware), ask: "This PR has observable behavior. Capture evidence for the PR description?" -This frame becomes the opening. For small+simple PRs, the "after" sentence alone may be the entire description. +- **Capture now** -- load the `ce-demo-reel` skill with a target description inferred from the branch diff. ce-demo-reel returns `Tier`, `Description`, and `URL`. Note the captured evidence so it can be passed as `focus:` context to `ce-pr-description` (e.g., "include the captured demo: <URL> as a `## Demo` section") or spliced into the returned body before apply. If capture returns `Tier: skipped` or `URL: "none"`, proceed with no evidence. +- **Use existing evidence** -- ask for the URL or markdown embed, then pass it via `focus:` or splice in before apply. +- **Skip** -- proceed with no evidence section. -#### Sizing the change +When evidence is not possible (docs-only, markdown-only, changelog-only, release metadata, CI/config-only, test-only, or pure internal refactors), skip without asking. -Assess size (files, diff volume) and complexity (design decisions, trade-offs, cross-cutting concerns) to select description depth: +**Delegate title and body generation to `ce-pr-description`.** Load the `ce-pr-description` skill with: -| Change profile | Description approach | -|---|---| -| Small + simple (typo, config, dep bump) | 1-2 sentences, no headers. Under ~300 characters. | -| Small + non-trivial (bugfix, behavioral change) | Short narrative, ~3-5 sentences. No headers unless two distinct concerns. | -| Medium feature or refactor | Narrative frame (before/after/scope), then what changed and why. Call out design decisions. | -| Large or architecturally significant | Full narrative: problem context, approach (and why), key decisions, migration/rollback if relevant. | -| Performance improvement | Include before/after measurements if available. Markdown table works well. | +- `range: <base-remote>/<base-branch>..HEAD` — for new PRs (no existing PR found in Step 3). +- `pr: <existing PR number>` — for existing PRs (found in Step 3) that should be refreshed. +- `focus: <hint>` — optional; include captured/linked evidence context or any user-supplied focus. -When in doubt, shorter is better. Match description weight to change weight. +`ce-pr-description` returns a `{title, body}` block. It applies the value-first writing principles, commit classification, sizing, narrative framing, writing voice, visual communication, numbering rules, and the Compound Engineering badge footer internally. Use the returned values verbatim in Step 7; do not layer manual edits onto them unless a focused adjustment is required (e.g., splicing an evidence block captured in this step that was not passed via `focus:`). -#### Writing voice - -If the user has documented style preferences, follow those. Otherwise: - -- Active voice. No em dashes or `--` substitutes; use periods, commas, colons, or parentheses. -- Vary sentence length. Never three similar-length sentences in a row. -- Do not make a claim and immediately explain it. Trust the reader. -- Plain English. Technical jargon fine; business jargon never. -- No filler: "it's worth noting", "importantly", "essentially", "in order to", "leverage", "utilize." -- Digits for numbers ("3 files"), not words ("three files"). - -#### Writing principles - -- **Lead with value**: Open with what's now possible or fixed, not what was moved around. The subtler failure is leading with the mechanism ("Replace the hardcoded capture block with a tiered skill") instead of the outcome ("Evidence capture now works for CLI tools and libraries, not just web apps"). -- **No orphaned opening paragraphs**: If the description uses `##` headings anywhere, the opening must also be under a heading (e.g., `## Summary`). For short descriptions with no sections, a bare paragraph is fine. -- **Describe the net result, not the journey**: The description covers the end state, not how you got there. No iteration history, debugging steps, intermediate failures, or bugs found and fixed during development. This applies equally to description updates: rewrite from the current state, not as a log of what changed since the last version. Exception: process details critical to understand a design choice. -- **When commits conflict, trust the final diff**: The commit list is supporting context, not the source of truth. If commits describe intermediate steps later revised or reverted, describe the end state from the full branch diff. -- **Explain the non-obvious**: If the diff is self-explanatory, don't narrate it. Spend space on things the diff doesn't show: why this approach, what was rejected, what the reviewer should watch. -- **Use structure when it earns its keep**: Headers, bullets, and tables aid comprehension, not mandatory template sections. -- **Markdown tables for data**: Before/after comparisons, performance numbers, or option trade-offs communicate well as tables. -- **No empty sections**: If a section doesn't apply, omit it. No "N/A" or "None." -- **Test plan -- only when non-obvious**: Include when testing requires edge cases the reviewer wouldn't think of, hard-to-verify behavior, or specific setup. Omit when "run the tests" is the only useful guidance. When the branch adds test files, name them with what they cover. - -#### Visual communication - -Include a visual aid only when the change is structurally complex enough that a reviewer would struggle to reconstruct the mental model from prose alone. - -**When to include:** - -| PR changes... | Visual aid | -|---|---| -| Architecture touching 3+ interacting components | Mermaid component or interaction diagram | -| Multi-step workflow or data flow with non-obvious sequencing | Mermaid flow diagram | -| 3+ behavioral modes, states, or variants | Markdown comparison table | -| Before/after performance or behavioral data | Markdown table | -| Data model changes with 3+ related entities | Mermaid ERD | - -**When to skip:** -- Sizing routes to "1-2 sentences" -- Prose already communicates clearly -- The diagram would just restate the diff visually -- Mechanical changes (renames, dep bumps, config, formatting) - -**Format:** -- **Mermaid** (default) for flows, interactions, dependencies. 5-10 nodes typical, up to 15 for genuinely complex changes. Use `TB` direction. Source should be readable as fallback. -- **ASCII diagrams** for annotated flows needing rich in-box content. 80-column max. -- **Markdown tables** for comparisons and decision matrices. -- Place inline at point of relevance, not in a separate section. -- Prose is authoritative when it conflicts with a visual. - -Verify generated diagrams against the change before including. - -#### Numbering and references - -Never prefix list items with `#` in PR descriptions -- GitHub interprets `#1`, `#2` as issue references and auto-links them. - -When referencing actual GitHub issues or PRs, use `org/repo#123` or the full URL. Never use bare `#123` unless verified. - -#### Compound Engineering badge - -Append a badge footer separated by a `---` rule. Skip if a badge already exists. - -**Badge:** - -```markdown ---- - -[![Compound Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin) -![HARNESS](https://img.shields.io/badge/MODEL_SLUG-COLOR?logo=LOGO&logoColor=white) -``` - -**Harness lookup:** - -| Harness | `LOGO` | `COLOR` | -|---------|--------|---------| -| Claude Code | `claude` | `D97757` | -| Codex | (omit logo param) | `000000` | -| Gemini CLI | `googlegemini` | `4285F4` | - -**Model slug:** Replace spaces with underscores. Append context window and thinking level in parentheses if known. Examples: `Opus_4.6_(1M,_Extended_Thinking)`, `Sonnet_4.6_(200K)`, `Gemini_3.1_Pro`. +If `ce-pr-description` returns a graceful-exit message instead of `{title, body}` (e.g., invalid range, closed PR), report the message and stop — do not create or edit the PR. ### Step 7: Create or update the PR #### New PR (no existing PR from Step 3) -```bash -gh pr create --title "the pr title" --body "$(cat <<'EOF' -PR description here +Using the `{title, body}` returned by `ce-pr-description`: ---- - -[![Compound Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin) -![Claude Code](https://img.shields.io/badge/Opus_4.6_(1M,_Extended_Thinking)-D97757?logo=claude&logoColor=white) +```bash +gh pr create --title "<returned title>" --body "$(cat <<'EOF' +<returned body> EOF )" ``` -Use the badge from the Compound Engineering badge section. Replace harness and model values from the lookup tables. Keep the PR title under 72 characters, following Step 2 conventions. +Keep the title under 72 characters; `ce-pr-description` already emits a conventional-commit title in that range. #### Existing PR (found in Step 3) The new commits are already on the PR from Step 5. Report the PR URL, then ask whether to rewrite the description. -- If **yes**: - 1. Classify commits -- new commits since last push are often fix-up work and should not appear as distinct items - 2. Size the full PR (not just new commits) using the sizing table - 3. **Rewrite from scratch** to describe the PR's net result as of now, following Step 6's writing principles. Do not append, amend, or layer onto the old description. The description is not a changelog. - 4. Include the Compound Engineering badge unless already present - 5. Apply: +- If **yes**, apply the returned title and body from `ce-pr-description`: ```bash - gh pr edit --body "$(cat <<'EOF' - Updated description here + gh pr edit --title "<returned title>" --body "$(cat <<'EOF' + <returned body> EOF )" ``` From 024625a4f8539d782bd27e1cfc707be37277911b Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 16:24:10 -0700 Subject: [PATCH 02/14] fix(ce-pr-description): handle fork PRs, non-local base refs, add trigger words MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses two issues flagged in PR #561 review. P1 — range: input no longer assumes the base ref is local. Fetches on demand before validating, matching the pre-refactor git-commit-push-pr behavior. Handles both remote-prefixed forms (origin/main) and bare branch names with a sensible remote-detection fallback. Previously a fresh clone or a deleted local branch caused an early graceful exit and skipped description generation entirely; now it fetches and proceeds. P2 — pr: input no longer uses headRefName directly in git commands. Fetches refs/pull/<number>/head (GitHub's always-available pull ref), captures FETCH_HEAD into PR_HEAD_SHA, and uses the SHA in subsequent merge-base/log/diff calls. Works for fork PRs, deleted local branches, and cases where the user runs the skill from a different branch. Documents an alternative SHA-from-gh-metadata path for non-GitHub remotes where pull/<N>/head isn't exposed. Also optimizes the frontmatter for skill triggering: - Description now leads with explicit trigger phrases ('write a PR description', 'refresh the PR description', 'regenerate the PR body', 'rewrite this PR', 'freshen the PR', etc.) so Claude reliably consults this skill for standalone description work, not only via the git-commit-push-pr and ce-pr-stack callers. - argument-hint surfaces the three input forms (pr: / range: / focus:) and notes that fork PRs and non-local base refs are handled automatically — so callers and humans know the skill is robust. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../skills/ce-pr-description/SKILL.md | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index e5e9af3d5..c2d459f1b 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -1,6 +1,7 @@ --- name: ce-pr-description -description: "Generate a value-first PR title and body for a GitHub pull request or arbitrary diff range, returning structured {title, body} output. Accepts either pr:<number> (existing PR) or range:<base>..<head> (pre-PR or dry-run), plus an optional focus hint. Used by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions); occasionally invoked directly when only a description rewrite is wanted. Does NOT apply the description — the caller decides whether and when to edit." +description: "Write or regenerate a value-first pull-request description (title + body) from an existing PR or a diff range. Use when the user says 'write a PR description', 'refresh the PR description', 'regenerate the PR body', 'rewrite this PR', 'freshen the PR', 'update the PR description', 'draft a PR body for this diff', 'describe this PR properly', 'generate the PR title', or wants a well-framed PR description without the full commit-and-push ceremony. Also used internally by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions) so all callers share one writing voice. Accepts pr:<number> (existing open PR) or range:<base>..<head> (pre-PR or dry-run), plus an optional focus:<hint>. Returns structured {title, body} for the caller to apply via gh pr edit or gh pr create — this skill never edits the PR itself and never prompts for confirmation." +argument-hint: "[pr:<number> | range:<base>..<head>] [focus:<hint>] — pr: reads an existing open PR, range: works from any diff (fork PRs and non-local base refs handled automatically), focus: optional steering hint" --- # CE PR Description @@ -55,39 +56,73 @@ gh pr view <number> --json number,state,title,body,baseRefName,headRefName,headR If the returned `state` is not `OPEN`, report "PR <number> is <state> (not open); cannot regenerate description" and exit gracefully without output. Callers expecting `{title, body}` must handle this empty case. -Resolve the base remote and base branch from the PR metadata. Fall back to `origin` as the remote when match detection is ambiguous. Derive the diff range as `<base-remote>/<baseRefName>...<headRefName>`. +Resolve the base remote from the PR metadata (the base repository's remote in the local clone — fall back to `origin` when ambiguous). Do not assume a local branch matching `headRefName` exists: the PR may come from a fork, the local branch may have been deleted, the clone may not have fetched it, or the user may be running the skill from a different branch. Resolve the PR's head commit via the **`refs/pull/<number>/head`** ref, which GitHub makes available on every PR regardless of source repository. -Verify the base remote-tracking ref exists; fetch if needed: +Fetch both the base ref (in case it isn't local yet) and the PR head ref in one step: ```bash -git rev-parse --verify <base-remote>/<baseRefName> 2>/dev/null || git fetch --no-tags <base-remote> <baseRefName> +git fetch --no-tags <base-remote> <baseRefName> "refs/pull/<number>/head" ``` -Gather merge base, commit list, and full diff: +The PR head SHA is then available as `FETCH_HEAD`. Capture it into a variable before running any subsequent git commands so later invocations of `git fetch` do not overwrite it: ```bash -MERGE_BASE=$(git merge-base <base-remote>/<baseRefName> <headRefName>) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..<headRefName> && echo '=== DIFF ===' && git diff $MERGE_BASE...<headRefName> +PR_HEAD_SHA=$(git rev-parse FETCH_HEAD) +``` + +Alternative path when `refs/pull/<number>/head` is unavailable (some ghes configurations or non-GitHub remotes): read the last commit SHA from the `commits` array returned by `gh pr view`'s `--json commits` output and use that SHA directly — no fetch needed because `gh` surfaces the SHA from the API even when the commit isn't local. Note that subsequent `git merge-base`/`git log`/`git diff` calls then require the commit to be fetched: `git fetch --no-tags <base-remote> <PR_HEAD_SHA>` is idempotent when the commit is already local. + +Gather merge base, commit list, and full diff using the resolved SHA (not `headRefName`): + +```bash +MERGE_BASE=$(git merge-base <base-remote>/<baseRefName> $PR_HEAD_SHA) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..$PR_HEAD_SHA && echo '=== DIFF ===' && git diff $MERGE_BASE...$PR_HEAD_SHA ``` Also capture the existing PR body for evidence preservation in Step 3. ### If input is `range: <base>..<head>` -Validate both endpoints resolve: +Resolve both endpoints, **fetching the base ref on demand** before validating. This is important because callers often pass remote-tracking refs (`origin/main`, `origin/develop`) or branch names that may not be present in the local clone — particularly after a fresh clone, after the local branch was deleted, or when targeting a non-default base. The pre-refactor behavior in `git-commit-push-pr` fetched on demand; preserve that here. + +Detect whether `<base>` looks like a remote-tracking ref (contains a `/` matching a known remote) or a bare branch name: ```bash -git rev-parse --verify <base> 2>/dev/null && git rev-parse --verify <head> 2>/dev/null +# If <base> is of the form <remote>/<branch>, try fetching that remote/branch first. +# If <base> is a bare branch name without a remote prefix, try the default remote (origin +# first, then the single remote if there's only one) as a fallback. +BASE=<base> +HEAD=<head> + +if ! git rev-parse --verify "$BASE" 2>/dev/null >/dev/null; then + if [[ "$BASE" == */* ]]; then + REMOTE=${BASE%%/*} + BRANCH=${BASE#*/} + git fetch --no-tags "$REMOTE" "$BRANCH" 2>/dev/null + else + # Try origin, then single-remote fallback + git fetch --no-tags origin "$BASE" 2>/dev/null || { + REMOTES=$(git remote) + REMOTE_COUNT=$(echo "$REMOTES" | grep -c .) + if [ "$REMOTE_COUNT" = "1" ]; then + git fetch --no-tags "$REMOTES" "$BASE" 2>/dev/null + fi + } + fi +fi + +# Validate after fetch attempt +git rev-parse --verify "$BASE" 2>/dev/null && git rev-parse --verify "$HEAD" 2>/dev/null ``` -If either fails, report "Invalid range: <base>..<head> -- <which endpoint> does not resolve" and exit gracefully without output. +If either endpoint still fails to resolve after the fetch attempt, report "Invalid range: `<base>..<head>` -- `<base>` does not resolve (tried local and remote)" or similar for `<head>`, and exit gracefully without output. Do not fabricate a description from a partial or empty diff. Gather merge base, commit list, and full diff: ```bash -MERGE_BASE=$(git merge-base <base> <head>) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..<head> && echo '=== DIFF ===' && git diff $MERGE_BASE...<head> +MERGE_BASE=$(git merge-base "$BASE" "$HEAD") && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE.."$HEAD" && echo '=== DIFF ===' && git diff $MERGE_BASE..."$HEAD" ``` -If the commit list is empty, report "No commits between <base> and <head>" and exit gracefully. +If the commit list is empty, report "No commits between `<base>` and `<head>`" and exit gracefully. --- From d37f492b6ca03e2502aa9cbef6db5e5717b25305 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 16:28:00 -0700 Subject: [PATCH 03/14] feat(ce-pr-description): accept URLs and cross-repo PR references for pr: input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pr: now takes three equivalent forms: - Bare number: pr: 561 (resolves in current repo) - Full URL: pr: https://github.com/owner/repo/pull/561 - Shorthand: pr: owner/repo#561 gh pr view accepts all three natively, so the metadata fetch is unchanged. Two runtime paths split on whether the PR's base repo matches the current working directory: Case A (same repo): local-git path — fetch refs/pull/<N>/head, use PR_HEAD_SHA in merge-base/log/diff. Existing behavior. Case B (different repo): API-only path — gh pr diff gives the diff without requiring the commits to be local. Equivalent output with a caller-facing note that API fallback was used (relevant for evidence preservation logic in Step 3). Enables workflows like pasting a PR URL from a repo the user doesn't have a local clone of, or refreshing descriptions across repos from a single working directory. Description and argument-hint updated to surface URL support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../skills/ce-pr-description/SKILL.md | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index c2d459f1b..75abee08c 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -1,7 +1,7 @@ --- name: ce-pr-description -description: "Write or regenerate a value-first pull-request description (title + body) from an existing PR or a diff range. Use when the user says 'write a PR description', 'refresh the PR description', 'regenerate the PR body', 'rewrite this PR', 'freshen the PR', 'update the PR description', 'draft a PR body for this diff', 'describe this PR properly', 'generate the PR title', or wants a well-framed PR description without the full commit-and-push ceremony. Also used internally by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions) so all callers share one writing voice. Accepts pr:<number> (existing open PR) or range:<base>..<head> (pre-PR or dry-run), plus an optional focus:<hint>. Returns structured {title, body} for the caller to apply via gh pr edit or gh pr create — this skill never edits the PR itself and never prompts for confirmation." -argument-hint: "[pr:<number> | range:<base>..<head>] [focus:<hint>] — pr: reads an existing open PR, range: works from any diff (fork PRs and non-local base refs handled automatically), focus: optional steering hint" +description: "Write or regenerate a value-first pull-request description (title + body) from an existing PR or a diff range. Use when the user says 'write a PR description', 'refresh the PR description', 'regenerate the PR body', 'rewrite this PR', 'freshen the PR', 'update the PR description', 'draft a PR body for this diff', 'describe this PR properly', 'generate the PR title', or pastes a GitHub PR URL and asks to rewrite or refresh its description. Also used internally by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions) so all callers share one writing voice. Accepts pr:<number-or-url> (existing open PR — bare number, full https://github.com/.../pull/NN URL, or owner/repo#NN shorthand), range:<base>..<head> (pre-PR or dry-run), plus an optional focus:<hint>. Returns structured {title, body} for the caller to apply via gh pr edit or gh pr create — this skill never edits the PR itself and never prompts for confirmation." +argument-hint: "[pr:<number-or-url> | range:<base>..<head>] [focus:<hint>] — pr: accepts a bare number, a full https://github.com/.../pull/NN URL, or owner/repo#NN shorthand; range: works from any diff (fork PRs and non-local base refs handled automatically); focus: optional steering hint" --- # CE PR Description @@ -18,7 +18,12 @@ Why a separate skill: several callers need the same writing logic without the si Callers pass one of the two input forms below, plus an optional focus hint. If invoked directly by the user with no explicit form, infer from context (an existing open PR on the current branch -> `pr:<number>`; a branch with no PR yet -> `range:<base>..HEAD`). -- **`pr: <number>`** -- generate description for an existing PR. The skill reads title, body, and commit list via `gh pr view`, and derives the diff from the PR's commit range. +- **`pr: <number-or-url>`** -- generate description for an existing PR. Accepts three forms: + - Bare number: `pr: 561` -- resolves against the current repo + - Full URL: `pr: https://github.com/owner/repo/pull/561` -- works from any directory, even outside a local clone of the target repo + - Shorthand: `pr: owner/repo#561` -- works anywhere + + The skill reads title, body, and commit list via `gh pr view <number-or-url>` (which accepts all three forms natively), and derives the diff from the PR's commit range. When the URL points to a repo other than the current working directory's repo, the skill fetches the PR head from the URL-derived remote; if that is not possible (no local clone of that repo), it falls back to reading the diff directly via `gh pr diff <number-or-url>` and skips the local-git steps. - **`range: <base>..<head>`** -- generate description for an arbitrary range without requiring an existing PR. Useful before a PR is created, or as a dry-run for a branch being prepared for stack submission. - **`focus: <hint>`** (optional) -- a user-provided steering note such as "include the benchmarking results" or "emphasize the migration safety story". Incorporate alongside the diff-derived narrative; do not let focus override the value-first principles. @@ -46,39 +51,53 @@ Interactive scaffolding (confirmation prompts, compare-and-confirm, apply step) ## Step 1: Resolve the diff and commit list -### If input is `pr: <number>` +### If input is `pr: <number-or-url>` -Fetch PR metadata and commit list: +All three input forms (bare number, full URL, `owner/repo#number` shorthand) work identically with `gh pr view` — pass the caller's value through verbatim as `<pr-ref>`: ```bash -gh pr view <number> --json number,state,title,body,baseRefName,headRefName,headRepositoryOwner,headRepository,commits,url +gh pr view <pr-ref> --json number,state,title,body,baseRefName,headRefName,headRepositoryOwner,headRepository,baseRepository,commits,url ``` +Extract the PR number, base-repo owner, and base-repo name from the JSON output. The PR number is needed for the `refs/pull/<N>/head` fetch; the base-repo identity determines whether the target repo matches the current working directory. + If the returned `state` is not `OPEN`, report "PR <number> is <state> (not open); cannot regenerate description" and exit gracefully without output. Callers expecting `{title, body}` must handle this empty case. -Resolve the base remote from the PR metadata (the base repository's remote in the local clone — fall back to `origin` when ambiguous). Do not assume a local branch matching `headRefName` exists: the PR may come from a fork, the local branch may have been deleted, the clone may not have fetched it, or the user may be running the skill from a different branch. Resolve the PR's head commit via the **`refs/pull/<number>/head`** ref, which GitHub makes available on every PR regardless of source repository. +**Determine whether the PR lives in the current repo** by comparing the PR's base-repo (owner + name) against the local clone's remote URL. Use `git remote get-url origin` and check whether the URL points to `<baseRepositoryOwner>/<baseRepository>`. Two cases: + +**Case A — PR is in the current repo** (common case when the user is working inside the target repo): + +Resolve the base remote (the one whose URL points to the PR's base repository; fall back to `origin`). Do not assume a local branch matching `headRefName` exists: the PR may come from a fork, the local branch may have been deleted, the clone may not have fetched it, or the user may be running the skill from a different branch. Resolve the PR's head commit via the **`refs/pull/<number>/head`** ref, which GitHub makes available on every PR regardless of source repository. Fetch both the base ref (in case it isn't local yet) and the PR head ref in one step: ```bash git fetch --no-tags <base-remote> <baseRefName> "refs/pull/<number>/head" +PR_HEAD_SHA=$(git rev-parse FETCH_HEAD) ``` -The PR head SHA is then available as `FETCH_HEAD`. Capture it into a variable before running any subsequent git commands so later invocations of `git fetch` do not overwrite it: +Capture `PR_HEAD_SHA` immediately so subsequent fetches do not overwrite `FETCH_HEAD`. + +Gather merge base, commit list, and full diff using the resolved SHA (not `headRefName`): ```bash -PR_HEAD_SHA=$(git rev-parse FETCH_HEAD) +MERGE_BASE=$(git merge-base <base-remote>/<baseRefName> $PR_HEAD_SHA) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..$PR_HEAD_SHA && echo '=== DIFF ===' && git diff $MERGE_BASE...$PR_HEAD_SHA ``` -Alternative path when `refs/pull/<number>/head` is unavailable (some ghes configurations or non-GitHub remotes): read the last commit SHA from the `commits` array returned by `gh pr view`'s `--json commits` output and use that SHA directly — no fetch needed because `gh` surfaces the SHA from the API even when the commit isn't local. Note that subsequent `git merge-base`/`git log`/`git diff` calls then require the commit to be fetched: `git fetch --no-tags <base-remote> <PR_HEAD_SHA>` is idempotent when the commit is already local. +**Case B — PR is in a different repo** (user pasted a URL to a repo not present locally): -Gather merge base, commit list, and full diff using the resolved SHA (not `headRefName`): +Skip the local-git path entirely. Read the diff directly via `gh`, which hits the GitHub API: ```bash -MERGE_BASE=$(git merge-base <base-remote>/<baseRefName> $PR_HEAD_SHA) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..$PR_HEAD_SHA && echo '=== DIFF ===' && git diff $MERGE_BASE...$PR_HEAD_SHA +gh pr diff <pr-ref> +gh pr view <pr-ref> --json commits --jq '.commits[] | [.oid[0:7], .messageHeadline] | @tsv' ``` -Also capture the existing PR body for evidence preservation in Step 3. +This gives an equivalent diff and commit list without requiring the base or head refs to be local. The rest of the pipeline (classification, framing, writing) proceeds unchanged. Note in the returned body or a short caller-facing note when this fallback was used — it signals that evidence preservation (Step 3) relied on the PR body as fetched from the API rather than on any local working-tree state. + +**Alternative path when `refs/pull/<number>/head` is unavailable** (some ghes configurations or non-GitHub remotes): read the last commit SHA from the `commits` array returned by `gh pr view`'s `--json commits` output and use that SHA directly — no named ref needed because `gh` surfaces the SHA from the API even when the commit isn't local. Subsequent `git` calls then require the commit to be fetched: `git fetch --no-tags <base-remote> <PR_HEAD_SHA>` is idempotent when the commit is already local. + +Also capture the existing PR body for evidence preservation in Step 3 (both cases). ### If input is `range: <base>..<head>` From 53dfb5e8416194a882cb8d67fc2ddfe7e5dbbb03 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 16:49:43 -0700 Subject: [PATCH 04/14] fix(ce-pr-description): correct JSON fields, strengthen FETCH_HEAD warning, sharpen diagram vs table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues surfaced by content-quality evals on PR #561. JSON field correction: gh pr view --json does NOT expose baseRepository. The SKILL.md previously requested it alongside headRepository, which would have failed at runtime. Corrected to headRepository + headRepositoryOwner + isCrossRepository (the fields that actually exist). Case A vs Case B routing now uses isCrossRepository + URL parsing against origin, not the nonexistent baseRepository. FETCH_HEAD handling: the warning to capture PR_HEAD_SHA immediately now explicitly forbids parallel commands and split tool calls between fetch and rev-parse. The ephemeral nature of FETCH_HEAD is the failure mode; any intervening fetch silently corrupts the SHA. Visual communication: reshape the "when to use Mermaid vs when to use a table" guidance around the real distinction — topology vs parallel variation. Architecture changes have edges (A → B, who talks to whom, who delegates to what), which tables cannot express; diagrams are almost always right for architecture. Tables are for parallel variation of a single shape (same metric different values, same attributes different variants). Adds an in-doubt heuristic: "edges → Mermaid, rows → table." Previously the guidance allowed tables as a substitute for architecture diagrams, which caused evals to pass the visual-aid assertion with flat lists where a graph was needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../skills/ce-pr-description/SKILL.md | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index 75abee08c..ed02b8d0a 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -56,27 +56,31 @@ Interactive scaffolding (confirmation prompts, compare-and-confirm, apply step) All three input forms (bare number, full URL, `owner/repo#number` shorthand) work identically with `gh pr view` — pass the caller's value through verbatim as `<pr-ref>`: ```bash -gh pr view <pr-ref> --json number,state,title,body,baseRefName,headRefName,headRepositoryOwner,headRepository,baseRepository,commits,url +gh pr view <pr-ref> --json number,state,title,body,baseRefName,headRefName,headRepository,headRepositoryOwner,isCrossRepository,commits,url ``` -Extract the PR number, base-repo owner, and base-repo name from the JSON output. The PR number is needed for the `refs/pull/<N>/head` fetch; the base-repo identity determines whether the target repo matches the current working directory. +Note the available JSON fields: `gh pr view --json` exposes `headRepository` (the PR source repo) and `isCrossRepository` (whether the PR source differs from the base). It does NOT expose a `baseRepository` field — the base repo is the repo queried by `gh pr view` itself, inferred from the URL or current working directory. Extract the PR number (for the `refs/pull/<N>/head` fetch) and use `isCrossRepository` to decide routing. If the returned `state` is not `OPEN`, report "PR <number> is <state> (not open); cannot regenerate description" and exit gracefully without output. Callers expecting `{title, body}` must handle this empty case. -**Determine whether the PR lives in the current repo** by comparing the PR's base-repo (owner + name) against the local clone's remote URL. Use `git remote get-url origin` and check whether the URL points to `<baseRepositoryOwner>/<baseRepository>`. Two cases: +**Determine whether the PR lives in the current working directory's repo.** Two independent signals: +- The input form: a bare number or an `owner/repo#NN` shorthand implicitly targets the current repo; a full URL may target any repo. +- The URL's repo identity: parse the URL's `<owner>/<repo>` path segments and compare against `git remote get-url origin` (strip `.git` suffix, handle `git@github.com:owner/repo` vs `https://github.com/owner/repo` forms). + +If the URL-derived repo matches the local `origin` remote's repo, route to Case A. Otherwise route to Case B. For bare-number and shorthand inputs that resolve against the current directory, always Case A. The two cases: **Case A — PR is in the current repo** (common case when the user is working inside the target repo): Resolve the base remote (the one whose URL points to the PR's base repository; fall back to `origin`). Do not assume a local branch matching `headRefName` exists: the PR may come from a fork, the local branch may have been deleted, the clone may not have fetched it, or the user may be running the skill from a different branch. Resolve the PR's head commit via the **`refs/pull/<number>/head`** ref, which GitHub makes available on every PR regardless of source repository. -Fetch both the base ref (in case it isn't local yet) and the PR head ref in one step: +Fetch both the base ref (in case it isn't local yet) and the PR head ref in one step, then capture the head SHA immediately: ```bash git fetch --no-tags <base-remote> <baseRefName> "refs/pull/<number>/head" PR_HEAD_SHA=$(git rev-parse FETCH_HEAD) ``` -Capture `PR_HEAD_SHA` immediately so subsequent fetches do not overwrite `FETCH_HEAD`. +**`FETCH_HEAD` is ephemeral.** Do not run other `git fetch` commands, do not dispatch parallel shell operations that might fetch, and do not separate the fetch and `rev-parse` steps across tool calls. Capture into `PR_HEAD_SHA` synchronously in the same shell invocation, then use `$PR_HEAD_SHA` for every subsequent git command. If anything else runs a fetch between these two steps, `FETCH_HEAD` will be overwritten and `rev-parse` will return the wrong SHA. Gather merge base, commit list, and full diff using the resolved SHA (not `headRefName`): @@ -232,26 +236,37 @@ If the repo has documented style preferences in context, follow those. Otherwise Include a visual aid only when the change is structurally complex enough that a reviewer would struggle to reconstruct the mental model from prose alone. -**When to include:** +**The core distinction — structure vs. parallel variation:** + +- Use a **Mermaid diagram** when the change has **topology** — components with directed relationships (calls, flows, dependencies, state transitions, data paths). Diagrams express "A talks to B, B talks to C, C does not talk back to A" in a way tables cannot. +- Use a **markdown table** when the change has **parallel variation of a single shape** — N things that share the same attributes but differ in their values. Tables express "option 1 costs X, option 2 costs Y, option 3 costs Z" cleanly. + +Architecture changes are almost always topology (components + edges), so Mermaid is usually the right call — a table of "components that interact" loses the edges and becomes a flat list. Reserve tables for genuinely parallel data: before/after measurements, option trade-offs, flag matrices, config enumerations. + +**When to include (prefer Mermaid, not a table, for architecture/flow):** | PR changes... | Visual aid | |---|---| -| Architecture touching 3+ interacting components | Mermaid component or interaction diagram | -| Multi-step workflow or data flow with non-obvious sequencing | Mermaid flow diagram | -| 3+ behavioral modes, states, or variants | Markdown comparison table | -| Before/after performance or behavioral data | Markdown table | -| Data model changes with 3+ related entities | Mermaid ERD | +| Architecture touching 3+ interacting components (the components have *directed relationships* — who calls whom, who owns what, which skill delegates to which) | **Mermaid** component or interaction diagram. Do not substitute a table — tables cannot show edges. | +| Multi-step workflow or data flow with non-obvious sequencing | **Mermaid** flow diagram | +| State machine with 3+ states and non-trivial transitions | **Mermaid** state diagram | +| Data model changes with 3+ related entities | **Mermaid** ERD | +| Before/after performance or behavioral measurements (same metric, different values) | **Markdown table** | +| Option or flag trade-offs (same attributes evaluated across variants) | **Markdown table** | +| Feature matrix / compatibility grid | **Markdown table** | + +**When in doubt, ask: "Does the information have edges (A → B) or does it have rows (attribute × variant)?"** Edges → Mermaid. Rows → table. Architecture has edges almost by definition. -**When to skip:** +**When to skip any visual:** - Sizing routes to "1-2 sentences" - Prose already communicates clearly - The diagram would just restate the diff visually - Mechanical changes (renames, dep bumps, config, formatting) -**Format:** -- **Mermaid** (default) for flows, interactions, dependencies. 5-10 nodes typical, up to 15 for genuinely complex changes. Use `TB` direction. Source should be readable as fallback. +**Format details:** +- **Mermaid** (default for topology). 5-10 nodes typical, up to 15 for genuinely complex changes. Use `TB` direction. Source should be readable as fallback. - **ASCII diagrams** for annotated flows needing rich in-box content. 80-column max. -- **Markdown tables** for comparisons and decision matrices. +- **Markdown tables** for parallel-variation data only. - Place inline at point of relevance, not in a separate section. - Prose is authoritative when it conflicts with a visual. From 30c87f87bf723202919e83acd43922eb0565819a Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 18:29:50 -0700 Subject: [PATCH 05/14] fix(ce-pr-description): use explicit SHA from gh pr view instead of FETCH_HEAD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous Case A path ran a multi-ref fetch and read FETCH_HEAD: git fetch --no-tags origin main refs/pull/<N>/head PR_HEAD_SHA=$(git rev-parse FETCH_HEAD) git rev-parse FETCH_HEAD returns only the first entry in .git/FETCH_HEAD, which is the base branch SHA, not the PR head. Silent failure — merge-base equals PR_HEAD_SHA, diff is empty, commit list is empty, and the skill produces a broken description with no error signal. Fix: extract the PR head SHA explicitly from gh pr view --json commits (the last entry of the commits array is the PR's current tip), then address all subsequent git commands by explicit SHA rather than via FETCH_HEAD. A single fetch populates the object store; downstream commands don't depend on ref ordering. Also documents a fallback that parses .git/FETCH_HEAD by pull-ref pattern via awk — robust to multi-ref ordering — for the rare case where a server refuses to serve non-tip SHAs directly. Caught during integration testing of git-commit-push-pr's refresh-mode delegation to ce-pr-description (PR #561). An agent following the old instructions silently produced an empty diff and had to work around by falling through to the SHA-from-commits-array path that was documented as an alternative — that path is now primary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../skills/ce-pr-description/SKILL.md | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index ed02b8d0a..6167376d4 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -71,23 +71,39 @@ If the URL-derived repo matches the local `origin` remote's repo, route to Case **Case A — PR is in the current repo** (common case when the user is working inside the target repo): -Resolve the base remote (the one whose URL points to the PR's base repository; fall back to `origin`). Do not assume a local branch matching `headRefName` exists: the PR may come from a fork, the local branch may have been deleted, the clone may not have fetched it, or the user may be running the skill from a different branch. Resolve the PR's head commit via the **`refs/pull/<number>/head`** ref, which GitHub makes available on every PR regardless of source repository. +Resolve the base remote (the one whose URL points to the PR's base repository; fall back to `origin`). Do not assume a local branch matching `headRefName` exists: the PR may come from a fork, the local branch may have been deleted, the clone may not have fetched it, or the user may be running the skill from a different branch. -Fetch both the base ref (in case it isn't local yet) and the PR head ref in one step, then capture the head SHA immediately: +Extract the PR head SHA directly from the `gh pr view --json commits` response. `commits` is an ordered array; the last entry is the PR's current tip. Extract it explicitly: ```bash -git fetch --no-tags <base-remote> <baseRefName> "refs/pull/<number>/head" -PR_HEAD_SHA=$(git rev-parse FETCH_HEAD) +PR_HEAD_SHA=$(gh pr view <pr-ref> --json commits --jq '.commits[-1].oid') ``` -**`FETCH_HEAD` is ephemeral.** Do not run other `git fetch` commands, do not dispatch parallel shell operations that might fetch, and do not separate the fetch and `rev-parse` steps across tool calls. Capture into `PR_HEAD_SHA` synchronously in the same shell invocation, then use `$PR_HEAD_SHA` for every subsequent git command. If anything else runs a fetch between these two steps, `FETCH_HEAD` will be overwritten and `rev-parse` will return the wrong SHA. +Using an explicit SHA avoids `FETCH_HEAD` entirely, which has a multi-ref ordering problem: `git fetch --no-tags <remote> <refA> <refB>` populates `.git/FETCH_HEAD` with both refs but `git rev-parse FETCH_HEAD` returns only the first entry's SHA. When the base ref comes before the PR head in the fetch command, `rev-parse` silently returns the base SHA and subsequent `git merge-base`/`git diff` calls produce an empty diff with no error signal. Using an explicit SHA sidesteps the issue. -Gather merge base, commit list, and full diff using the resolved SHA (not `headRefName`): +Now fetch the base ref (to ensure it's current locally) and the PR head SHA (to ensure the commit's object is in the local store — required by `git merge-base`, `git log`, `git diff`): + +```bash +git fetch --no-tags <base-remote> <baseRefName> $PR_HEAD_SHA +``` + +This is a single fetch, safe to run with any ref ordering because downstream commands address commits by explicit SHA, not via `FETCH_HEAD`. The fetch is idempotent when both the base and the SHA are already local. + +Gather merge base, commit list, and full diff using the explicit SHA: ```bash MERGE_BASE=$(git merge-base <base-remote>/<baseRefName> $PR_HEAD_SHA) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..$PR_HEAD_SHA && echo '=== DIFF ===' && git diff $MERGE_BASE...$PR_HEAD_SHA ``` +If the explicit-SHA fetch fails (the server refuses to serve a non-tip SHA — rare on GitHub, possible on some GHES configurations), fall back to fetching `refs/pull/<number>/head` and reading `PR_HEAD_SHA` from `.git/FETCH_HEAD` by pull-ref pattern rather than from `git rev-parse FETCH_HEAD`: + +```bash +git fetch --no-tags <base-remote> "refs/pull/<number>/head" +PR_HEAD_SHA=$(awk '/refs\/pull\/[0-9]+\/head/ {print $1; exit}' "$(git rev-parse --git-dir)/FETCH_HEAD") +``` + +This grep-based extraction is robust against multi-ref ordering because it matches the specific ref by pattern rather than relying on `FETCH_HEAD`'s first-entry semantics. + **Case B — PR is in a different repo** (user pasted a URL to a repo not present locally): Skip the local-git path entirely. Read the diff directly via `gh`, which hits the GitHub API: @@ -99,8 +115,6 @@ gh pr view <pr-ref> --json commits --jq '.commits[] | [.oid[0:7], .messageHeadli This gives an equivalent diff and commit list without requiring the base or head refs to be local. The rest of the pipeline (classification, framing, writing) proceeds unchanged. Note in the returned body or a short caller-facing note when this fallback was used — it signals that evidence preservation (Step 3) relied on the PR body as fetched from the API rather than on any local working-tree state. -**Alternative path when `refs/pull/<number>/head` is unavailable** (some ghes configurations or non-GitHub remotes): read the last commit SHA from the `commits` array returned by `gh pr view`'s `--json commits` output and use that SHA directly — no named ref needed because `gh` surfaces the SHA from the API even when the commit isn't local. Subsequent `git` calls then require the commit to be fetched: `git fetch --no-tags <base-remote> <PR_HEAD_SHA>` is idempotent when the commit is already local. - Also capture the existing PR body for evidence preservation in Step 3 (both cases). ### If input is `range: <base>..<head>` From c871100f82756c845b1f672386dde636a434e4d1 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 18:35:30 -0700 Subject: [PATCH 06/14] fix(ce-pr-description): drop wrong owner/repo#NN claim, use headRefOid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues surfaced by exercising the full invocation matrix. owner/repo#NN shorthand was claimed as a third input form, but gh pr view's positional argument accepts only <number>, <url>, or <branch> — NOT shorthand. Passing owner/repo#NN produces "no pull requests found for branch" errors. Corrected the input-form documentation to the two forms that actually work: bare number (current repo) and full URL (any repo). Added a note that cross-repo number references are done via gh pr view <N> -R owner/repo, which callers can use directly when needed. A full URL remains the simplest cross-repo path. headRefOid is a direct JSON field exposed by gh pr view --json. Using it is more robust than indexing into .commits[-1].oid, which depended on the commits array being non-empty and correctly ordered. The FETCH_HEAD multi-ref bug fix from the previous commit is preserved — both paths produce the same SHA — but headRefOid is now the primary recommendation. Also added baseRefOid to the documented field list. Validated end-to-end against the invocation matrix: - pr: bare number (same repo) — headRefOid path resolves correctly - pr: full URL (same repo) — URL parse + headRefOid resolves correctly - pr: full URL (different repo) — Case B API fallback kicks in - range: origin/main..HEAD — resolves directly - range: main..HEAD (not local) — fetch-on-demand works - range: HEAD~1..HEAD — commit-ish resolves without fetch - pr: nonexistent — gh surfaces GraphQL error, graceful exit - pr: merged/closed PR — state check exits gracefully - range: bogus base — rev-parse fails, graceful exit Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../skills/ce-pr-description/SKILL.md | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index 6167376d4..3cb3fcf00 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -1,7 +1,7 @@ --- name: ce-pr-description -description: "Write or regenerate a value-first pull-request description (title + body) from an existing PR or a diff range. Use when the user says 'write a PR description', 'refresh the PR description', 'regenerate the PR body', 'rewrite this PR', 'freshen the PR', 'update the PR description', 'draft a PR body for this diff', 'describe this PR properly', 'generate the PR title', or pastes a GitHub PR URL and asks to rewrite or refresh its description. Also used internally by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions) so all callers share one writing voice. Accepts pr:<number-or-url> (existing open PR — bare number, full https://github.com/.../pull/NN URL, or owner/repo#NN shorthand), range:<base>..<head> (pre-PR or dry-run), plus an optional focus:<hint>. Returns structured {title, body} for the caller to apply via gh pr edit or gh pr create — this skill never edits the PR itself and never prompts for confirmation." -argument-hint: "[pr:<number-or-url> | range:<base>..<head>] [focus:<hint>] — pr: accepts a bare number, a full https://github.com/.../pull/NN URL, or owner/repo#NN shorthand; range: works from any diff (fork PRs and non-local base refs handled automatically); focus: optional steering hint" +description: "Write or regenerate a value-first pull-request description (title + body) from an existing PR or a diff range. Use when the user says 'write a PR description', 'refresh the PR description', 'regenerate the PR body', 'rewrite this PR', 'freshen the PR', 'update the PR description', 'draft a PR body for this diff', 'describe this PR properly', 'generate the PR title', or pastes a GitHub PR URL and asks to rewrite or refresh its description. Also used internally by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions) so all callers share one writing voice. Accepts pr:<number-or-url> (existing open PR — bare number for the current repo, or a full https://github.com/.../pull/NN URL for any repo), range:<base>..<head> (pre-PR or dry-run), plus an optional focus:<hint>. Returns structured {title, body} for the caller to apply via gh pr edit or gh pr create — this skill never edits the PR itself and never prompts for confirmation." +argument-hint: "[pr:<number-or-url> | range:<base>..<head>] [focus:<hint>] — pr: accepts a bare number (current repo) or a full https://github.com/.../pull/NN URL (any repo); range: works from any diff (fork PRs and non-local base refs handled automatically); focus: optional steering hint" --- # CE PR Description @@ -18,12 +18,13 @@ Why a separate skill: several callers need the same writing logic without the si Callers pass one of the two input forms below, plus an optional focus hint. If invoked directly by the user with no explicit form, infer from context (an existing open PR on the current branch -> `pr:<number>`; a branch with no PR yet -> `range:<base>..HEAD`). -- **`pr: <number-or-url>`** -- generate description for an existing PR. Accepts three forms: +- **`pr: <number-or-url>`** -- generate description for an existing PR. Two input forms: - Bare number: `pr: 561` -- resolves against the current repo - - Full URL: `pr: https://github.com/owner/repo/pull/561` -- works from any directory, even outside a local clone of the target repo - - Shorthand: `pr: owner/repo#561` -- works anywhere + - Full URL: `pr: https://github.com/owner/repo/pull/561` -- works from any directory, including when the target repo is not cloned locally - The skill reads title, body, and commit list via `gh pr view <number-or-url>` (which accepts all three forms natively), and derives the diff from the PR's commit range. When the URL points to a repo other than the current working directory's repo, the skill fetches the PR head from the URL-derived remote; if that is not possible (no local clone of that repo), it falls back to reading the diff directly via `gh pr diff <number-or-url>` and skips the local-git steps. + The skill reads title, body, `headRefOid` (the PR head SHA), and commit list via `gh pr view <number-or-url>`, then derives the diff from the PR's commit range. When the URL points to a repo other than the current working directory's repo, the skill falls back to reading the diff directly via `gh pr diff <number-or-url>` and skips the local-git steps. + + *Note:* `gh pr view` positional arguments accept only `<number>`, `<url>`, or `<branch>` — NOT `owner/repo#NN` shorthand. If a caller wants to target a cross-repo PR without using a full URL, they pass the number plus `-R owner/repo` to `gh pr view`; this skill accepts the same equivalent by detecting a bare number and using `-R` when the caller also provides a repo hint. In practice, a full URL is the simplest cross-repo path and what most callers will use. - **`range: <base>..<head>`** -- generate description for an arbitrary range without requiring an existing PR. Useful before a PR is created, or as a dry-run for a branch being prepared for stack submission. - **`focus: <hint>`** (optional) -- a user-provided steering note such as "include the benchmarking results" or "emphasize the migration safety story". Incorporate alongside the diff-derived narrative; do not let focus override the value-first principles. @@ -53,13 +54,13 @@ Interactive scaffolding (confirmation prompts, compare-and-confirm, apply step) ### If input is `pr: <number-or-url>` -All three input forms (bare number, full URL, `owner/repo#number` shorthand) work identically with `gh pr view` — pass the caller's value through verbatim as `<pr-ref>`: +Both input forms (bare number, full URL) work identically with `gh pr view` — pass the caller's value through verbatim as `<pr-ref>`: ```bash -gh pr view <pr-ref> --json number,state,title,body,baseRefName,headRefName,headRepository,headRepositoryOwner,isCrossRepository,commits,url +gh pr view <pr-ref> --json number,state,title,body,baseRefName,baseRefOid,headRefName,headRefOid,headRepository,headRepositoryOwner,isCrossRepository,commits,url ``` -Note the available JSON fields: `gh pr view --json` exposes `headRepository` (the PR source repo) and `isCrossRepository` (whether the PR source differs from the base). It does NOT expose a `baseRepository` field — the base repo is the repo queried by `gh pr view` itself, inferred from the URL or current working directory. Extract the PR number (for the `refs/pull/<N>/head` fetch) and use `isCrossRepository` to decide routing. +Note the available JSON fields: `gh pr view --json` exposes `headRefOid` (the PR head SHA directly — prefer this over indexing into the `commits` array), `baseRefOid` (the base-branch SHA), `headRepository` + `headRepositoryOwner` (the PR source repo), and `isCrossRepository` (whether the PR source differs from the base). It does NOT expose a `baseRepository` field — the base repo is the repo queried by `gh pr view` itself, inferred from the URL or current working directory. The positional argument form accepts only `<number>`, `<url>`, or `<branch>` — NOT `owner/repo#NN` shorthand. For cross-repo number references, use `gh pr view <N> -R owner/repo` instead. If the returned `state` is not `OPEN`, report "PR <number> is <state> (not open); cannot regenerate description" and exit gracefully without output. Callers expecting `{title, body}` must handle this empty case. @@ -73,13 +74,13 @@ If the URL-derived repo matches the local `origin` remote's repo, route to Case Resolve the base remote (the one whose URL points to the PR's base repository; fall back to `origin`). Do not assume a local branch matching `headRefName` exists: the PR may come from a fork, the local branch may have been deleted, the clone may not have fetched it, or the user may be running the skill from a different branch. -Extract the PR head SHA directly from the `gh pr view --json commits` response. `commits` is an ordered array; the last entry is the PR's current tip. Extract it explicitly: +Read the PR head SHA directly from the `headRefOid` field returned by the initial `gh pr view --json` call above — no second `gh` call needed. If you want a standalone probe: ```bash -PR_HEAD_SHA=$(gh pr view <pr-ref> --json commits --jq '.commits[-1].oid') +PR_HEAD_SHA=$(gh pr view <pr-ref> --json headRefOid --jq '.headRefOid') ``` -Using an explicit SHA avoids `FETCH_HEAD` entirely, which has a multi-ref ordering problem: `git fetch --no-tags <remote> <refA> <refB>` populates `.git/FETCH_HEAD` with both refs but `git rev-parse FETCH_HEAD` returns only the first entry's SHA. When the base ref comes before the PR head in the fetch command, `rev-parse` silently returns the base SHA and subsequent `git merge-base`/`git diff` calls produce an empty diff with no error signal. Using an explicit SHA sidesteps the issue. +Using `headRefOid` is more direct than indexing into `.commits[-1].oid` (no dependency on the commits array being non-empty or correctly ordered), and avoids `FETCH_HEAD` entirely. `FETCH_HEAD` has a multi-ref ordering problem: `git fetch --no-tags <remote> <refA> <refB>` populates `.git/FETCH_HEAD` with both refs but `git rev-parse FETCH_HEAD` returns only the first entry's SHA. When the base ref comes before the PR head in the fetch command, `rev-parse` silently returns the base SHA and subsequent `git merge-base`/`git diff` calls produce an empty diff with no error signal. Using an explicit SHA sidesteps the issue. Now fetch the base ref (to ensure it's current locally) and the PR head SHA (to ensure the commit's object is in the local store — required by `git merge-base`, `git log`, `git diff`): From e768a9564079767a59e14bc57a9b6882c8733dc1 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 18:39:31 -0700 Subject: [PATCH 07/14] chore(ce-pr-description): trim argument-hint to the signature only argument-hint is meant to be a quick argument shape for discovery, not a full spec. The long-form explanations belong in the description (where they are) and the body. Cut from 280+ characters to just the signature. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- plugins/compound-engineering/skills/ce-pr-description/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index 3cb3fcf00..5e284d762 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -1,7 +1,7 @@ --- name: ce-pr-description description: "Write or regenerate a value-first pull-request description (title + body) from an existing PR or a diff range. Use when the user says 'write a PR description', 'refresh the PR description', 'regenerate the PR body', 'rewrite this PR', 'freshen the PR', 'update the PR description', 'draft a PR body for this diff', 'describe this PR properly', 'generate the PR title', or pastes a GitHub PR URL and asks to rewrite or refresh its description. Also used internally by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions) so all callers share one writing voice. Accepts pr:<number-or-url> (existing open PR — bare number for the current repo, or a full https://github.com/.../pull/NN URL for any repo), range:<base>..<head> (pre-PR or dry-run), plus an optional focus:<hint>. Returns structured {title, body} for the caller to apply via gh pr edit or gh pr create — this skill never edits the PR itself and never prompts for confirmation." -argument-hint: "[pr:<number-or-url> | range:<base>..<head>] [focus:<hint>] — pr: accepts a bare number (current repo) or a full https://github.com/.../pull/NN URL (any repo); range: works from any diff (fork PRs and non-local base refs handled automatically); focus: optional steering hint" +argument-hint: "[pr:<number-or-url> | range:<base>..<head>] [focus:<hint>]" --- # CE PR Description From d9c975d5f5c1f10802836a643f9e1c05cadeb123 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 19:49:31 -0700 Subject: [PATCH 08/14] Address PR review feedback (#561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cluster A — git-commit-push-pr orchestration gaps from Unit 5 refactor: - DU-3 and Step 6 existing-PR delegation now pass full PR URL (unambiguous repo/PR identity) to ce-pr-description instead of bare number - Existing-PR flow reordered: Step 7 asks rewrite-or-not first, Step 6 runs only on yes, so the delegation call site is actually reached - Step 6 gathers real branch diff (git diff <remote>/<base>...HEAD) before the evidence decision gate, so feature branches with pushed commits are judged on the branch diff not the working-tree diff Cluster B — ce-pr-description input-handling correctness: - range: base resolution now treats <remote>/<branch> and <bare-branch> as distinct possibilities. Tries local first, then matched-remote prefix, then bare branch across remotes. release/2026-04, feat/foo, hotfix/x now resolve correctly via origin. - EFFECTIVE_BASE tracks the actually-resolving ref separately from user-supplied <base>; used in all downstream merge-base/log/diff. Fixes range:main..HEAD in clones where local main was deleted. - pr: Case A now falls back to Case B API path if fetch fails or merge-base can't resolve — covers shallow clones, detached states, offline/auth issues without hard-failing the skill. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../skills/ce-pr-description/SKILL.md | 61 +++++++++++-------- .../skills/git-commit-push-pr/SKILL.md | 21 +++++-- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index 5e284d762..fed9e2e72 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -68,7 +68,11 @@ If the returned `state` is not `OPEN`, report "PR <number> is <state> (not open) - The input form: a bare number or an `owner/repo#NN` shorthand implicitly targets the current repo; a full URL may target any repo. - The URL's repo identity: parse the URL's `<owner>/<repo>` path segments and compare against `git remote get-url origin` (strip `.git` suffix, handle `git@github.com:owner/repo` vs `https://github.com/owner/repo` forms). -If the URL-derived repo matches the local `origin` remote's repo, route to Case A. Otherwise route to Case B. For bare-number and shorthand inputs that resolve against the current directory, always Case A. The two cases: +If the URL-derived repo matches the local `origin` remote's repo, route to Case A. Otherwise route to Case B. For bare-number and shorthand inputs that resolve against the current directory, always Case A. + +**Case A fallback to Case B:** Even when the URL-derived repo matches `origin`, the current working directory may not be a usable clone for this PR's refs — for example, the user may be running the skill from a shallow clone, a detached state missing the base branch, or a directory where `git fetch` cannot reach the remote (auth, offline, GHES quirks). If Case A's fetch-and-resolve path fails (the base-ref fetch errors out, or `git merge-base` cannot resolve the merge base after fetch), fall back to Case B rather than failing the skill. Record that the fallback occurred and note it in the caller-facing output as described in Case B. Do not retry Case A. + +The two cases: **Case A — PR is in the current repo** (common case when the user is working inside the target repo): @@ -122,42 +126,51 @@ Also capture the existing PR body for evidence preservation in Step 3 (both case Resolve both endpoints, **fetching the base ref on demand** before validating. This is important because callers often pass remote-tracking refs (`origin/main`, `origin/develop`) or branch names that may not be present in the local clone — particularly after a fresh clone, after the local branch was deleted, or when targeting a non-default base. The pre-refactor behavior in `git-commit-push-pr` fetched on demand; preserve that here. -Detect whether `<base>` looks like a remote-tracking ref (contains a `/` matching a known remote) or a bare branch name: +Resolving `<base>` is subtle because a `/` in the name does not reliably indicate a remote prefix — `release/2026-04`, `feat/foo`, and `hotfix/x` are all valid bare branch names that contain `/`. Resolve in this order, and track the "effective base ref" (`EFFECTIVE_BASE`) — the ref that actually resolves locally after any fetch — separately from the user-supplied `<base>`. Use `EFFECTIVE_BASE` in all downstream `git merge-base`, `git log`, and `git diff` calls, because a successful `git fetch origin main` leaves the base at `origin/main`, not `main` (this skill does not check out branches), and validating the bare name after fetch would spuriously fail in clones with no local `main`. + +1. **Try `<base>` as-is locally.** If `git rev-parse --verify "<base>"` succeeds, set `EFFECTIVE_BASE=<base>` and skip to validation. +2. **If `<base>` contains `/` and the prefix is a known remote** (appears in `git remote` output): try `git fetch --no-tags <remote> <branch>`. On success, set `EFFECTIVE_BASE=<base>` (it is already the `<remote>/<branch>` form) and skip to validation. On fetch failure, continue to step 3 — do not exit yet, because the name may also be a valid bare branch with a `/` in it. +3. **Try `<base>` as a bare branch on each remote.** For `origin` first, then for any remaining remotes, attempt `git fetch --no-tags <remote> <base>`. On the first success, set `EFFECTIVE_BASE=<remote>/<base>` and skip to validation. When only one remote exists, this is equivalent to the single-remote fallback. +4. **Validate.** Confirm `git rev-parse --verify "$EFFECTIVE_BASE"` and `git rev-parse --verify "<head>"` both succeed. If `EFFECTIVE_BASE` is unset (no step produced a resolving ref), or validation fails, report the invalid-range error and exit. ```bash -# If <base> is of the form <remote>/<branch>, try fetching that remote/branch first. -# If <base> is a bare branch name without a remote prefix, try the default remote (origin -# first, then the single remote if there's only one) as a fallback. BASE=<base> HEAD=<head> - -if ! git rev-parse --verify "$BASE" 2>/dev/null >/dev/null; then - if [[ "$BASE" == */* ]]; then - REMOTE=${BASE%%/*} - BRANCH=${BASE#*/} - git fetch --no-tags "$REMOTE" "$BRANCH" 2>/dev/null - else - # Try origin, then single-remote fallback - git fetch --no-tags origin "$BASE" 2>/dev/null || { - REMOTES=$(git remote) - REMOTE_COUNT=$(echo "$REMOTES" | grep -c .) - if [ "$REMOTE_COUNT" = "1" ]; then - git fetch --no-tags "$REMOTES" "$BASE" 2>/dev/null - fi - } +EFFECTIVE_BASE="" + +if git rev-parse --verify "$BASE" >/dev/null 2>&1; then + EFFECTIVE_BASE="$BASE" +elif [[ "$BASE" == */* ]] && git remote | grep -qx "${BASE%%/*}"; then + REMOTE="${BASE%%/*}" + BRANCH="${BASE#*/}" + if git fetch --no-tags "$REMOTE" "$BRANCH"; then + EFFECTIVE_BASE="$BASE" fi fi -# Validate after fetch attempt -git rev-parse --verify "$BASE" 2>/dev/null && git rev-parse --verify "$HEAD" 2>/dev/null +if [ -z "$EFFECTIVE_BASE" ]; then + # Try as a bare branch name on origin first, then any other remote. + REMOTES=$(git remote) + ORDERED=$(printf '%s\n' "$REMOTES" | awk '/^origin$/ {print; next} {others = others "\n" $0} END {if (others) print substr(others, 2)}') + for R in $ORDERED; do + if git fetch --no-tags "$R" "$BASE"; then + EFFECTIVE_BASE="$R/$BASE" + break + fi + done +fi + +# Final validation: both endpoints must resolve. +git rev-parse --verify "$EFFECTIVE_BASE" >/dev/null 2>&1 +git rev-parse --verify "$HEAD" >/dev/null 2>&1 ``` -If either endpoint still fails to resolve after the fetch attempt, report "Invalid range: `<base>..<head>` -- `<base>` does not resolve (tried local and remote)" or similar for `<head>`, and exit gracefully without output. Do not fabricate a description from a partial or empty diff. +If either endpoint still fails to resolve after the fetch attempts, report "Invalid range: `<base>..<head>` -- `<base>` does not resolve (tried local and remote)" or similar for `<head>`, and exit gracefully without output. Do not fabricate a description from a partial or empty diff. Gather merge base, commit list, and full diff: ```bash -MERGE_BASE=$(git merge-base "$BASE" "$HEAD") && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE.."$HEAD" && echo '=== DIFF ===' && git diff $MERGE_BASE..."$HEAD" +MERGE_BASE=$(git merge-base "$EFFECTIVE_BASE" "$HEAD") && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE.."$HEAD" && echo '=== DIFF ===' && git diff $MERGE_BASE..."$HEAD" ``` If the commit list is empty, report "No commits between `<base>` and `<head>`" and exit gracefully. diff --git a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md index ebb501403..3ebf1d5cf 100644 --- a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md +++ b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md @@ -59,7 +59,7 @@ Ask the user: "Update the PR description for this branch?" If declined, stop. ### DU-2: Find the PR -Use the current branch and existing PR check from context. If the current branch is empty (detached HEAD), report no branch and stop. If the PR check returned `state: OPEN`, proceed to DU-3. Otherwise, report no open PR and stop. +Use the current branch and existing PR check from context. If the current branch is empty (detached HEAD), report no branch and stop. If the PR check returned `state: OPEN`, note the PR `url` from the context block — this is the unambiguous reference to pass downstream — and proceed to DU-3. Otherwise, report no open PR and stop. ### DU-3: Write and apply the updated description @@ -69,7 +69,7 @@ Read the current PR description to drive the compare-and-confirm step later: gh pr view --json body --jq '.body' ``` -**Generate the updated title and body** — load the `ce-pr-description` skill with `pr: <PR number from DU-2>`. If the user provided a focus (e.g., "include the benchmarking results"), pass it as `focus: <hint>`. The skill returns a `{title, body}` block without applying or prompting. +**Generate the updated title and body** — load the `ce-pr-description` skill with `pr: <PR URL from DU-2>`. Use the full PR URL (e.g., `https://github.com/owner/repo/pull/123`) rather than a bare number so the delegate preserves repo/PR identity context and works correctly even when invoked from a worktree or subdirectory where the current repo is ambiguous. If the user provided a focus (e.g., "include the benchmarking results"), pass it as `focus: <hint>`. The skill returns a `{title, body}` block without applying or prompting. If `ce-pr-description` returns a "not open" or other graceful-exit message instead of a `{title, body}` pair, report that message and stop. @@ -137,7 +137,7 @@ Priority order for commit messages and PR titles: Use the current branch and existing PR check from context. If the branch is empty, report detached HEAD and stop. -If the PR check returned `state: OPEN`, note the URL -- continue to Step 4 and 5, then skip to Step 7 (existing PR flow). Otherwise, continue through Step 8. +If the PR check returned `state: OPEN`, note the URL -- this is the existing-PR flow. Continue to Step 4 and 5 (commit any pending work and push), then go to Step 7 to ask whether to rewrite the description. Only run Step 6 (which generates a new description via `ce-pr-description`) if the user confirms the rewrite; Step 7's existing-PR sub-path consumes the `{title, body}` that Step 6 produces. Otherwise (no open PR), continue through Steps 6, 7, and 8 in order. ### Step 4: Branch, stage, and commit @@ -182,6 +182,15 @@ The working-tree diff from Step 1 only shows uncommitted changes at invocation t If none resolve, ask the user to specify the target branch. +**Gather the full branch diff (before evidence decision).** The working-tree diff from Step 1 only reflects uncommitted changes at invocation time — on the common "feature branch, all pushed, open PR" path, Step 1 skips the commit/push steps and the working-tree diff is empty. The evidence decision below needs the real branch diff to judge whether behavior is observable, so compute it explicitly against the base resolved above: + +```bash +git fetch --no-tags <base-remote> <base-branch> +git diff <base-remote>/<base-branch>...HEAD +``` + +Use this branch diff (not the working-tree diff) for the evidence decision. If the branch diff is empty (e.g., HEAD is already merged into the base or the branch has no unique commits), skip the evidence prompt and continue to delegation. + **Evidence decision (before delegation).** If the branch diff changes observable behavior (UI, CLI output, API behavior with runnable code, generated artifacts, workflow output) and evidence is not otherwise blocked (unavailable credentials, paid services, deploy-only infrastructure, hardware), ask: "This PR has observable behavior. Capture evidence for the PR description?" - **Capture now** -- load the `ce-demo-reel` skill with a target description inferred from the branch diff. ce-demo-reel returns `Tier`, `Description`, and `URL`. Note the captured evidence so it can be passed as `focus:` context to `ce-pr-description` (e.g., "include the captured demo: <URL> as a `## Demo` section") or spliced into the returned body before apply. If capture returns `Tier: skipped` or `URL: "none"`, proceed with no evidence. @@ -193,7 +202,7 @@ When evidence is not possible (docs-only, markdown-only, changelog-only, release **Delegate title and body generation to `ce-pr-description`.** Load the `ce-pr-description` skill with: - `range: <base-remote>/<base-branch>..HEAD` — for new PRs (no existing PR found in Step 3). -- `pr: <existing PR number>` — for existing PRs (found in Step 3) that should be refreshed. +- `pr: <existing PR URL>` — for existing PRs (found in Step 3) that should be refreshed. Pass the full PR URL from the Step 3 context (e.g., `https://github.com/owner/repo/pull/123`) rather than a bare number so the delegate preserves repo/PR identity context. - `focus: <hint>` — optional; include captured/linked evidence context or any user-supplied focus. `ce-pr-description` returns a `{title, body}` block. It applies the value-first writing principles, commit classification, sizing, narrative framing, writing voice, visual communication, numbering rules, and the Compound Engineering badge footer internally. Use the returned values verbatim in Step 7; do not layer manual edits onto them unless a focused adjustment is required (e.g., splicing an evidence block captured in this step that was not passed via `focus:`). @@ -219,7 +228,7 @@ Keep the title under 72 characters; `ce-pr-description` already emits a conventi The new commits are already on the PR from Step 5. Report the PR URL, then ask whether to rewrite the description. -- If **yes**, apply the returned title and body from `ce-pr-description`: +- If **yes**, run Step 6 now to generate `{title, body}` via `ce-pr-description` (passing the existing PR URL as `pr:`), then apply the returned title and body: ```bash gh pr edit --title "<returned title>" --body "$(cat <<'EOF' @@ -228,7 +237,7 @@ The new commits are already on the PR from Step 5. Report the PR URL, then ask w )" ``` -- If **no** -- done. +- If **no** -- skip Step 6 entirely and finish. Do not run delegation or evidence capture when the user declined the rewrite. ### Step 8: Report From 103248df15c8f3f99e54b85d836653f85c1a0a94 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 20:38:11 -0700 Subject: [PATCH 09/14] refactor(ce-pr-description): two modes + natural input parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the pr:/range:/focus: formal grammar with two modes and free-form input parsing: 1. Current-branch mode (default) — when no PR reference is given, describe the commits on HEAD against the repo's default base (origin/HEAD), or against an existing PR's baseRefName if a PR is already open on the branch. 2. PR mode — when the input contains any PR reference (full URL, pr:<N>, #NN, or a bare number), describe that PR using the existing Case A/B routing. Focus is now free-form steering text. The agent extracts whichever PR reference pattern is present; everything else is treated as focus. No formal focus: keyword required (though it still works). Removes the range: input surface entirely: - No more <remote>/<branch> vs bare-branch ambiguity - No more EFFECTIVE_BASE tracking - No more multi-remote resolution cascade - No more fetch-on-demand logic for arbitrary user input - All three Cluster B feedback threads were about this complexity; the problem class now doesn't exist. Current-branch mode uses a narrow, well-defined base resolution: origin/HEAD or the existing PR's base. Callers like git-commit-push-pr never hit the problematic edge cases because they operate on well-known local refs. Updates git-commit-push-pr's Step 6 and DU-3 to match the new contract: invoke ce-pr-description with no arg (current-branch mode, new PRs) or with the PR URL (existing PRs). Focus is appended as free text rather than passed as a formal focus: argument. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../skills/ce-pr-description/SKILL.md | 172 ++++++++---------- .../skills/git-commit-push-pr/SKILL.md | 15 +- 2 files changed, 83 insertions(+), 104 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index fed9e2e72..65055e74f 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -1,12 +1,12 @@ --- name: ce-pr-description -description: "Write or regenerate a value-first pull-request description (title + body) from an existing PR or a diff range. Use when the user says 'write a PR description', 'refresh the PR description', 'regenerate the PR body', 'rewrite this PR', 'freshen the PR', 'update the PR description', 'draft a PR body for this diff', 'describe this PR properly', 'generate the PR title', or pastes a GitHub PR URL and asks to rewrite or refresh its description. Also used internally by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions) so all callers share one writing voice. Accepts pr:<number-or-url> (existing open PR — bare number for the current repo, or a full https://github.com/.../pull/NN URL for any repo), range:<base>..<head> (pre-PR or dry-run), plus an optional focus:<hint>. Returns structured {title, body} for the caller to apply via gh pr edit or gh pr create — this skill never edits the PR itself and never prompts for confirmation." -argument-hint: "[pr:<number-or-url> | range:<base>..<head>] [focus:<hint>]" +description: "Write or regenerate a value-first pull-request description (title + body) for the current branch's commits or for a specified PR. Use when the user says 'write a PR description', 'refresh the PR description', 'regenerate the PR body', 'rewrite this PR', 'freshen the PR', 'update the PR description', 'draft a PR body for this diff', 'describe this PR properly', 'generate the PR title', or pastes a GitHub PR URL / #NN / number. Also used internally by git-commit-push-pr (single-PR flow) and ce-pr-stack (per-layer stack descriptions) so all callers share one writing voice. Input is a natural-language prompt. A PR reference (a full GitHub PR URL, `pr:561`, `#561`, or a bare number alone) picks a specific PR; anything else is treated as optional steering for the default 'describe my current branch' mode. Returns structured {title, body} for the caller to apply via gh pr edit or gh pr create — this skill never edits the PR itself and never prompts for confirmation." +argument-hint: "[PR ref e.g. pr:561 | #561 | URL] [free-text steering]" --- # CE PR Description -Generate a conventional-commit-style title and a value-first body for a GitHub pull request from either an existing PR (`pr:<number>`) or a raw diff range (`range:<base>..<head>`). Returns structured `{title, body}` for the caller to apply — this skill never invokes `gh pr edit` or `gh pr create`, and never prompts for interactive confirmation. +Generate a conventional-commit-style title and a value-first body describing a pull request's work. Returns structured `{title, body}` for the caller to apply — this skill never invokes `gh pr edit` or `gh pr create`, and never prompts for interactive confirmation. Why a separate skill: several callers need the same writing logic without the single-PR interactive scaffolding that lives in `git-commit-push-pr`. `ce-pr-stack`'s splitting workflow runs this once per layer as a batch; `git-commit-push-pr` runs it inside its full-flow and refresh-mode paths. Extracting keeps one source of truth for the writing principles. @@ -16,17 +16,29 @@ Why a separate skill: several callers need the same writing logic without the si ## Inputs -Callers pass one of the two input forms below, plus an optional focus hint. If invoked directly by the user with no explicit form, infer from context (an existing open PR on the current branch -> `pr:<number>`; a branch with no PR yet -> `range:<base>..HEAD`). +Input is a free-form prompt. Parse it into two parts: -- **`pr: <number-or-url>`** -- generate description for an existing PR. Two input forms: - - Bare number: `pr: 561` -- resolves against the current repo - - Full URL: `pr: https://github.com/owner/repo/pull/561` -- works from any directory, including when the target repo is not cloned locally +- **A PR reference, if present.** Any of these patterns counts: a full GitHub PR URL (`https://github.com/owner/repo/pull/NN`), `pr:<number>` or `pr:<URL>`, a bare hashmark form (`#NN`), or the argument being just a number (`561`). Extract the PR reference and treat the rest of the argument as steering text. +- **Everything else is steering text** (a "focus" hint like "emphasize the benchmarks" or "do a good job with the perf story"). It may be combined with a PR reference or stand alone. - The skill reads title, body, `headRefOid` (the PR head SHA), and commit list via `gh pr view <number-or-url>`, then derives the diff from the PR's commit range. When the URL points to a repo other than the current working directory's repo, the skill falls back to reading the diff directly via `gh pr diff <number-or-url>` and skips the local-git steps. +No specific grammar is required — read the argument as natural language and identify whichever PR reference is present. If no PR reference is present, default to describing the current branch. - *Note:* `gh pr view` positional arguments accept only `<number>`, `<url>`, or `<branch>` — NOT `owner/repo#NN` shorthand. If a caller wants to target a cross-repo PR without using a full URL, they pass the number plus `-R owner/repo` to `gh pr view`; this skill accepts the same equivalent by detecting a bare number and using `-R` when the caller also provides a repo hint. In practice, a full URL is the simplest cross-repo path and what most callers will use. -- **`range: <base>..<head>`** -- generate description for an arbitrary range without requiring an existing PR. Useful before a PR is created, or as a dry-run for a branch being prepared for stack submission. -- **`focus: <hint>`** (optional) -- a user-provided steering note such as "include the benchmarking results" or "emphasize the migration safety story". Incorporate alongside the diff-derived narrative; do not let focus override the value-first principles. +### Mode selection + +| What the caller passes | Mode | +|---|---| +| No PR reference (empty argument or steering text only) | **Current-branch mode** — describe the commits on HEAD vs the repo's default base | +| A PR reference (URL, `pr:`, `#NN`, or bare number) | **PR mode** — describe the specified PR | + +Steering text is always optional. If present, incorporate it alongside the diff-derived narrative; do not let it override the value-first principles or fabricate content unsupported by the diff. + +**Examples**: + +- `ce-pr-description` → current-branch, no focus +- `ce-pr-description emphasize the benchmarks` → current-branch, focus = "emphasize the benchmarks" +- `ce-pr-description pr:561` → PR #561, no focus +- `ce-pr-description #561 do a good job with the perf story` → PR #561, focus = "do a good job with the perf story" +- `ce-pr-description https://github.com/foo/bar/pull/561 emphasize safety` → PR #561 in foo/bar, focus = "emphasize safety" ## Output @@ -42,7 +54,7 @@ The caller decides whether to apply via `gh pr edit`, `gh pr create`, or discard ## What this skill does not do - No interactive confirmation prompts. If the diff is ambiguous about something important (e.g., the focus hint conflicts with the actual changes), surface the ambiguity in the returned output or raise it to the caller — do not prompt the user directly. -- No branch checkout or assumption that HEAD is the target branch. Work from the input (`pr:` or `range:`) only. +- No branch checkout. Current-branch mode describes the HEAD in the user's current checkout; PR mode describes the specified PR. Neither mode checks out a different branch. - No compare-and-confirm narrative ("here's what changed since the last version"). The description describes the end state; the caller owns any compare-and-confirm framing. - No auto-apply via `gh pr edit` or `gh pr create`. Return the output and stop. @@ -52,129 +64,97 @@ Interactive scaffolding (confirmation prompts, compare-and-confirm, apply step) ## Step 1: Resolve the diff and commit list -### If input is `pr: <number-or-url>` +Parse the input (see Inputs above) and branch on which mode it selects. -Both input forms (bare number, full URL) work identically with `gh pr view` — pass the caller's value through verbatim as `<pr-ref>`: +### Current-branch mode (default when no PR reference was given) -```bash -gh pr view <pr-ref> --json number,state,title,body,baseRefName,baseRefOid,headRefName,headRefOid,headRepository,headRepositoryOwner,isCrossRepository,commits,url -``` +Determine the base against which to compare: -Note the available JSON fields: `gh pr view --json` exposes `headRefOid` (the PR head SHA directly — prefer this over indexing into the `commits` array), `baseRefOid` (the base-branch SHA), `headRepository` + `headRepositoryOwner` (the PR source repo), and `isCrossRepository` (whether the PR source differs from the base). It does NOT expose a `baseRepository` field — the base repo is the repo queried by `gh pr view` itself, inferred from the URL or current working directory. The positional argument form accepts only `<number>`, `<url>`, or `<branch>` — NOT `owner/repo#NN` shorthand. For cross-repo number references, use `gh pr view <N> -R owner/repo` instead. +1. Check whether the current branch already has an open PR on this repo. If yes, use that PR's `baseRefName` as the base — this handles the case of a feature branch targeting a non-default base (e.g., `develop`). +2. Otherwise fall back to the repo's default base (`origin/HEAD`). -If the returned `state` is not `OPEN`, report "PR <number> is <state> (not open); cannot regenerate description" and exit gracefully without output. Callers expecting `{title, body}` must handle this empty case. +```bash +# Detect current branch (fail if detached HEAD) +CURRENT_BRANCH=$(git branch --show-current) +if [ -z "$CURRENT_BRANCH" ]; then + echo "Detached HEAD — current-branch mode requires a branch. Pass a PR reference instead." + exit 1 +fi -**Determine whether the PR lives in the current working directory's repo.** Two independent signals: -- The input form: a bare number or an `owner/repo#NN` shorthand implicitly targets the current repo; a full URL may target any repo. -- The URL's repo identity: parse the URL's `<owner>/<repo>` path segments and compare against `git remote get-url origin` (strip `.git` suffix, handle `git@github.com:owner/repo` vs `https://github.com/owner/repo` forms). +# Prefer an existing PR's base, fall back to origin/HEAD +EXISTING_PR_BASE=$(gh pr view --json baseRefName --jq '.baseRefName' 2>/dev/null) +if [ -n "$EXISTING_PR_BASE" ]; then + BASE_REF="origin/$EXISTING_PR_BASE" +else + # Default base from origin/HEAD (the repo's default branch) + BASE_REF=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null) + BASE_REF="${BASE_REF:-origin/main}" +fi +``` + +If `$BASE_REF` does not resolve locally (`git rev-parse --verify "$BASE_REF"` fails), the caller (or the user) needs to fetch it first. Exit gracefully with `"Base ref $BASE_REF does not resolve locally. Fetch it before invoking the skill."` — do not attempt recovery. -If the URL-derived repo matches the local `origin` remote's repo, route to Case A. Otherwise route to Case B. For bare-number and shorthand inputs that resolve against the current directory, always Case A. +Gather merge base, commit list, and full diff: -**Case A fallback to Case B:** Even when the URL-derived repo matches `origin`, the current working directory may not be a usable clone for this PR's refs — for example, the user may be running the skill from a shallow clone, a detached state missing the base branch, or a directory where `git fetch` cannot reach the remote (auth, offline, GHES quirks). If Case A's fetch-and-resolve path fails (the base-ref fetch errors out, or `git merge-base` cannot resolve the merge base after fetch), fall back to Case B rather than failing the skill. Record that the fallback occurred and note it in the caller-facing output as described in Case B. Do not retry Case A. +```bash +MERGE_BASE=$(git merge-base "$BASE_REF" HEAD) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..HEAD && echo '=== DIFF ===' && git diff $MERGE_BASE...HEAD +``` -The two cases: +If the commit list is empty, report `"No commits between $BASE_REF and HEAD"` and exit gracefully — there is nothing to describe. -**Case A — PR is in the current repo** (common case when the user is working inside the target repo): +If an existing PR was found in step 1, also capture its body for evidence preservation in Step 3. -Resolve the base remote (the one whose URL points to the PR's base repository; fall back to `origin`). Do not assume a local branch matching `headRefName` exists: the PR may come from a fork, the local branch may have been deleted, the clone may not have fetched it, or the user may be running the skill from a different branch. +### PR mode (when the input contained a PR reference) -Read the PR head SHA directly from the `headRefOid` field returned by the initial `gh pr view --json` call above — no second `gh` call needed. If you want a standalone probe: +Normalize the reference into a form `gh pr view` accepts: a bare number (`561`), a full URL (`https://github.com/owner/repo/pull/561`), or the number extracted from `pr:561` or `#561`. `gh pr view`'s positional argument accepts bare numbers, URLs, and branch names — not `owner/repo#NN` shorthand. For a cross-repo number reference without a URL, the caller would use `-R owner/repo`; this skill accepts a full URL as the simplest cross-repo path, and that's what most callers use. ```bash -PR_HEAD_SHA=$(gh pr view <pr-ref> --json headRefOid --jq '.headRefOid') +gh pr view <pr-ref> --json number,state,title,body,baseRefName,baseRefOid,headRefName,headRefOid,headRepository,headRepositoryOwner,isCrossRepository,commits,url ``` -Using `headRefOid` is more direct than indexing into `.commits[-1].oid` (no dependency on the commits array being non-empty or correctly ordered), and avoids `FETCH_HEAD` entirely. `FETCH_HEAD` has a multi-ref ordering problem: `git fetch --no-tags <remote> <refA> <refB>` populates `.git/FETCH_HEAD` with both refs but `git rev-parse FETCH_HEAD` returns only the first entry's SHA. When the base ref comes before the PR head in the fetch command, `rev-parse` silently returns the base SHA and subsequent `git merge-base`/`git diff` calls produce an empty diff with no error signal. Using an explicit SHA sidesteps the issue. +Key JSON fields: `headRefOid` (PR head SHA — prefer over indexing into `commits`), `baseRefOid` (base-branch SHA), `headRepository` + `headRepositoryOwner` (PR source repo), `isCrossRepository`. There is no `baseRepository` field — the base repo is the one queried by `gh pr view` itself. + +If the returned `state` is not `OPEN`, report `"PR <number> is <state> (not open); cannot regenerate description"` and exit gracefully without output. Callers expecting `{title, body}` must handle this empty case. + +**Determine whether the PR lives in the current working directory's repo** by parsing the URL's `<owner>/<repo>` path segments and comparing against `git remote get-url origin` (strip `.git` suffix; handle both `git@github.com:owner/repo` and `https://github.com/owner/repo` forms). If the URL repo matches `origin`'s repo, route to the local-git path (Case A). Otherwise route to the API-only path (Case B). Bare numbers and `#NN` forms implicitly target the current repo → Case A. + +**Case A → Case B fallback:** Even when the URL repo matches `origin`, the local clone may not be usable for this PR's refs — shallow clone, detached state missing the base branch, offline, auth issues, GHES quirks. If Case A's fetch or `git merge-base` fails, fall back to Case B rather than failing the skill. Note the fallback in the caller-facing output. + +**Case A — PR is in the current repo:** -Now fetch the base ref (to ensure it's current locally) and the PR head SHA (to ensure the commit's object is in the local store — required by `git merge-base`, `git log`, `git diff`): +Read the PR head SHA directly from `headRefOid` in the JSON response above. Fetch the base ref and the head SHA in one call (the fetch is idempotent when refs are already local): ```bash -git fetch --no-tags <base-remote> <baseRefName> $PR_HEAD_SHA +PR_HEAD_SHA=<headRefOid from JSON> +git fetch --no-tags origin <baseRefName> $PR_HEAD_SHA ``` -This is a single fetch, safe to run with any ref ordering because downstream commands address commits by explicit SHA, not via `FETCH_HEAD`. The fetch is idempotent when both the base and the SHA are already local. - -Gather merge base, commit list, and full diff using the explicit SHA: +Using the explicit `$PR_HEAD_SHA` in downstream commands avoids `FETCH_HEAD`'s multi-ref ordering problem (`git rev-parse FETCH_HEAD` returns only the first fetched ref's SHA, which silently breaks a multi-ref fetch). ```bash -MERGE_BASE=$(git merge-base <base-remote>/<baseRefName> $PR_HEAD_SHA) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..$PR_HEAD_SHA && echo '=== DIFF ===' && git diff $MERGE_BASE...$PR_HEAD_SHA +MERGE_BASE=$(git merge-base origin/<baseRefName> $PR_HEAD_SHA) && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE..$PR_HEAD_SHA && echo '=== DIFF ===' && git diff $MERGE_BASE...$PR_HEAD_SHA ``` -If the explicit-SHA fetch fails (the server refuses to serve a non-tip SHA — rare on GitHub, possible on some GHES configurations), fall back to fetching `refs/pull/<number>/head` and reading `PR_HEAD_SHA` from `.git/FETCH_HEAD` by pull-ref pattern rather than from `git rev-parse FETCH_HEAD`: +If the explicit-SHA fetch is rejected (rare on GitHub, possible on some GHES configurations that disallow fetching non-tip SHAs), fall back to fetching `refs/pull/<number>/head` and reading the PR head SHA from `.git/FETCH_HEAD` by pull-ref pattern: ```bash -git fetch --no-tags <base-remote> "refs/pull/<number>/head" +git fetch --no-tags origin "refs/pull/<number>/head" PR_HEAD_SHA=$(awk '/refs\/pull\/[0-9]+\/head/ {print $1; exit}' "$(git rev-parse --git-dir)/FETCH_HEAD") ``` -This grep-based extraction is robust against multi-ref ordering because it matches the specific ref by pattern rather than relying on `FETCH_HEAD`'s first-entry semantics. - -**Case B — PR is in a different repo** (user pasted a URL to a repo not present locally): +**Case B — PR is in a different repo:** -Skip the local-git path entirely. Read the diff directly via `gh`, which hits the GitHub API: +Skip local git entirely. Read the diff and commit list from the API: ```bash gh pr diff <pr-ref> gh pr view <pr-ref> --json commits --jq '.commits[] | [.oid[0:7], .messageHeadline] | @tsv' ``` -This gives an equivalent diff and commit list without requiring the base or head refs to be local. The rest of the pipeline (classification, framing, writing) proceeds unchanged. Note in the returned body or a short caller-facing note when this fallback was used — it signals that evidence preservation (Step 3) relied on the PR body as fetched from the API rather than on any local working-tree state. +Same classification/framing/writing pipeline. Note in the caller-facing output that the API fallback was used. Also capture the existing PR body for evidence preservation in Step 3 (both cases). -### If input is `range: <base>..<head>` - -Resolve both endpoints, **fetching the base ref on demand** before validating. This is important because callers often pass remote-tracking refs (`origin/main`, `origin/develop`) or branch names that may not be present in the local clone — particularly after a fresh clone, after the local branch was deleted, or when targeting a non-default base. The pre-refactor behavior in `git-commit-push-pr` fetched on demand; preserve that here. - -Resolving `<base>` is subtle because a `/` in the name does not reliably indicate a remote prefix — `release/2026-04`, `feat/foo`, and `hotfix/x` are all valid bare branch names that contain `/`. Resolve in this order, and track the "effective base ref" (`EFFECTIVE_BASE`) — the ref that actually resolves locally after any fetch — separately from the user-supplied `<base>`. Use `EFFECTIVE_BASE` in all downstream `git merge-base`, `git log`, and `git diff` calls, because a successful `git fetch origin main` leaves the base at `origin/main`, not `main` (this skill does not check out branches), and validating the bare name after fetch would spuriously fail in clones with no local `main`. - -1. **Try `<base>` as-is locally.** If `git rev-parse --verify "<base>"` succeeds, set `EFFECTIVE_BASE=<base>` and skip to validation. -2. **If `<base>` contains `/` and the prefix is a known remote** (appears in `git remote` output): try `git fetch --no-tags <remote> <branch>`. On success, set `EFFECTIVE_BASE=<base>` (it is already the `<remote>/<branch>` form) and skip to validation. On fetch failure, continue to step 3 — do not exit yet, because the name may also be a valid bare branch with a `/` in it. -3. **Try `<base>` as a bare branch on each remote.** For `origin` first, then for any remaining remotes, attempt `git fetch --no-tags <remote> <base>`. On the first success, set `EFFECTIVE_BASE=<remote>/<base>` and skip to validation. When only one remote exists, this is equivalent to the single-remote fallback. -4. **Validate.** Confirm `git rev-parse --verify "$EFFECTIVE_BASE"` and `git rev-parse --verify "<head>"` both succeed. If `EFFECTIVE_BASE` is unset (no step produced a resolving ref), or validation fails, report the invalid-range error and exit. - -```bash -BASE=<base> -HEAD=<head> -EFFECTIVE_BASE="" - -if git rev-parse --verify "$BASE" >/dev/null 2>&1; then - EFFECTIVE_BASE="$BASE" -elif [[ "$BASE" == */* ]] && git remote | grep -qx "${BASE%%/*}"; then - REMOTE="${BASE%%/*}" - BRANCH="${BASE#*/}" - if git fetch --no-tags "$REMOTE" "$BRANCH"; then - EFFECTIVE_BASE="$BASE" - fi -fi - -if [ -z "$EFFECTIVE_BASE" ]; then - # Try as a bare branch name on origin first, then any other remote. - REMOTES=$(git remote) - ORDERED=$(printf '%s\n' "$REMOTES" | awk '/^origin$/ {print; next} {others = others "\n" $0} END {if (others) print substr(others, 2)}') - for R in $ORDERED; do - if git fetch --no-tags "$R" "$BASE"; then - EFFECTIVE_BASE="$R/$BASE" - break - fi - done -fi - -# Final validation: both endpoints must resolve. -git rev-parse --verify "$EFFECTIVE_BASE" >/dev/null 2>&1 -git rev-parse --verify "$HEAD" >/dev/null 2>&1 -``` - -If either endpoint still fails to resolve after the fetch attempts, report "Invalid range: `<base>..<head>` -- `<base>` does not resolve (tried local and remote)" or similar for `<head>`, and exit gracefully without output. Do not fabricate a description from a partial or empty diff. - -Gather merge base, commit list, and full diff: - -```bash -MERGE_BASE=$(git merge-base "$EFFECTIVE_BASE" "$HEAD") && echo "MERGE_BASE=$MERGE_BASE" && echo '=== COMMITS ===' && git log --oneline $MERGE_BASE.."$HEAD" && echo '=== DIFF ===' && git diff $MERGE_BASE..."$HEAD" -``` - -If the commit list is empty, report "No commits between `<base>` and `<head>`" and exit gracefully. - --- ## Step 2: Classify commits before writing diff --git a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md index 3ebf1d5cf..e17c20ae3 100644 --- a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md +++ b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md @@ -69,11 +69,11 @@ Read the current PR description to drive the compare-and-confirm step later: gh pr view --json body --jq '.body' ``` -**Generate the updated title and body** — load the `ce-pr-description` skill with `pr: <PR URL from DU-2>`. Use the full PR URL (e.g., `https://github.com/owner/repo/pull/123`) rather than a bare number so the delegate preserves repo/PR identity context and works correctly even when invoked from a worktree or subdirectory where the current repo is ambiguous. If the user provided a focus (e.g., "include the benchmarking results"), pass it as `focus: <hint>`. The skill returns a `{title, body}` block without applying or prompting. +**Generate the updated title and body** — load the `ce-pr-description` skill with the PR URL from DU-2 (e.g., `https://github.com/owner/repo/pull/123`). The URL preserves repo/PR identity even when invoked from a worktree or subdirectory where the current repo is ambiguous. If the user provided a focus (e.g., "include the benchmarking results"), append it as free-text steering after the URL. The skill returns a `{title, body}` block without applying or prompting. If `ce-pr-description` returns a "not open" or other graceful-exit message instead of a `{title, body}` pair, report that message and stop. -**Evidence decision:** `ce-pr-description` preserves any existing `## Demo` or `## Screenshots` block from the current body by default. If the user's focus asks to refresh or remove evidence, pass that intent via the `focus:` hint — the skill will honor it. If no evidence block exists and one would benefit the reader, invoke `ce-demo-reel` separately to capture, then re-invoke `ce-pr-description` with an updated focus that references the captured evidence. +**Evidence decision:** `ce-pr-description` preserves any existing `## Demo` or `## Screenshots` block from the current body by default. If the user's focus asks to refresh or remove evidence, pass that intent as steering text — the skill will honor it. If no evidence block exists and one would benefit the reader, invoke `ce-demo-reel` separately to capture, then re-invoke `ce-pr-description` with updated steering that references the captured evidence. **Compare and confirm** — briefly explain what the new description covers differently from the old one. This helps the user decide whether to apply; the description itself does not narrate these differences. @@ -199,15 +199,14 @@ Use this branch diff (not the working-tree diff) for the evidence decision. If t When evidence is not possible (docs-only, markdown-only, changelog-only, release metadata, CI/config-only, test-only, or pure internal refactors), skip without asking. -**Delegate title and body generation to `ce-pr-description`.** Load the `ce-pr-description` skill with: +**Delegate title and body generation to `ce-pr-description`.** Load the `ce-pr-description` skill: -- `range: <base-remote>/<base-branch>..HEAD` — for new PRs (no existing PR found in Step 3). -- `pr: <existing PR URL>` — for existing PRs (found in Step 3) that should be refreshed. Pass the full PR URL from the Step 3 context (e.g., `https://github.com/owner/repo/pull/123`) rather than a bare number so the delegate preserves repo/PR identity context. -- `focus: <hint>` — optional; include captured/linked evidence context or any user-supplied focus. +- **For a new PR** (no existing PR found in Step 3): invoke with no PR reference. `ce-pr-description`'s current-branch mode auto-detects `origin/HEAD` (or an existing PR's base, if one somehow appears between Step 3 and now) and describes the commits on HEAD. Any captured-evidence context or user focus is passed as free-text steering (e.g., "include the captured demo: <URL> as a `## Demo` section"). +- **For an existing PR** (found in Step 3): invoke with the full PR URL from the Step 3 context (e.g., `https://github.com/owner/repo/pull/123`). The URL preserves repo/PR identity even when invoked from a worktree or subdirectory. Append any focus steering as free text after the URL. -`ce-pr-description` returns a `{title, body}` block. It applies the value-first writing principles, commit classification, sizing, narrative framing, writing voice, visual communication, numbering rules, and the Compound Engineering badge footer internally. Use the returned values verbatim in Step 7; do not layer manual edits onto them unless a focused adjustment is required (e.g., splicing an evidence block captured in this step that was not passed via `focus:`). +`ce-pr-description` returns a `{title, body}` block. It applies the value-first writing principles, commit classification, sizing, narrative framing, writing voice, visual communication, numbering rules, and the Compound Engineering badge footer internally. Use the returned values verbatim in Step 7; do not layer manual edits onto them unless a focused adjustment is required (e.g., splicing an evidence block captured in this step that was not passed as steering text). -If `ce-pr-description` returns a graceful-exit message instead of `{title, body}` (e.g., invalid range, closed PR), report the message and stop — do not create or edit the PR. +If `ce-pr-description` returns a graceful-exit message instead of `{title, body}` (e.g., closed PR, no commits to describe, base ref unresolved), report the message and stop — do not create or edit the PR. ### Step 7: Create or update the PR From dcae7d3b2f63fcec5230f3e9f44f31e99d2ee171 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 20:45:44 -0700 Subject: [PATCH 10/14] Address PR review feedback (#561) Evidence preservation now triggers for any PR mode invocation, not only the bare-number form. After the earlier refactor, callers like git-commit-push-pr pass full PR URLs for repo-safe delegation, which made the conditional fall through to "omit entirely" and silently dropped existing ## Demo / ## Screenshots blocks during refresh. Preservation is about whether we have an existing PR body to read, not about which input shape the caller used. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../compound-engineering/skills/ce-pr-description/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index 65055e74f..25e99779a 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -180,8 +180,8 @@ Decide whether evidence capture is possible from the full branch diff. **This skill does NOT prompt the user** to capture evidence. The decision logic is: -1. **Input was `pr:<number>` and the existing body contains a `## Demo` or `## Screenshots` section with image embeds:** preserve it verbatim unless the `focus:` hint asks to refresh or remove it. Include the preserved block in the returned body. -2. **Otherwise:** omit the evidence section entirely. If the caller wants to capture evidence, the caller is responsible for invoking `ce-demo-reel` separately and splicing the result in, or for asking this skill to regenerate with an updated focus hint after capture. +1. **PR mode invocation** (any form: bare number, `#NN`, `pr:<N>`, or a full URL — anything that resolves to an existing PR whose body we fetched) **and the existing body contains a `## Demo` or `## Screenshots` section with image embeds:** preserve it verbatim unless the steering text asks to refresh or remove it. Include the preserved block in the returned body. This applies regardless of which input shape the caller used; what matters is that a PR exists and its body was read. +2. **Current-branch mode or PR mode without an evidence block:** omit the evidence section entirely. If the caller wants to capture evidence, the caller is responsible for invoking `ce-demo-reel` separately and splicing the result in, or for asking this skill to regenerate with updated steering text after capture. Do not label test output as "Demo" or "Screenshots". Place any preserved evidence block before the Compound Engineering badge. From f2bef3be58cac5e5b8923ee6e445d84de1a96182 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 20:47:31 -0700 Subject: [PATCH 11/14] Address PR review feedback (#561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add base:<ref> override for current-branch mode so callers can forward an already-resolved base branch. Non-default base targets (develop, release/*, or any branch set as the PR's base in git-commit-push-pr's Step 6) now generate descriptions from the correct commit range. Base resolution priority in current-branch mode: 1. Caller-supplied base:<ref> 2. Existing PR's baseRefName (for branches with a PR already open) 3. origin/HEAD (repo default) Updates git-commit-push-pr Step 6 to pass base:<base-remote>/<base-branch> when invoking ce-pr-description for new PRs, so description scope matches the branch actually being opened. PR mode is unchanged — PRs define their own base via baseRefName. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../skills/ce-pr-description/SKILL.md | 30 ++++++++++++------- .../skills/git-commit-push-pr/SKILL.md | 4 +-- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md index 25e99779a..018a188f7 100644 --- a/plugins/compound-engineering/skills/ce-pr-description/SKILL.md +++ b/plugins/compound-engineering/skills/ce-pr-description/SKILL.md @@ -32,10 +32,14 @@ No specific grammar is required — read the argument as natural language and id Steering text is always optional. If present, incorporate it alongside the diff-derived narrative; do not let it override the value-first principles or fabricate content unsupported by the diff. +**Optional `base:<ref>` override (current-branch mode only).** When a caller already knows the intended base branch (e.g., `git-commit-push-pr` has detected `origin/develop` or `origin/release/2026-04` as the target), it can pass `base:<ref>` to pin the base explicitly. The ref must resolve locally. This overrides auto-detection for current-branch mode; PR mode ignores it (PRs already define their own base via `baseRefName`). Most invocations don't need this — auto-detection (existing PR's `baseRefName` → `origin/HEAD`) covers the common case. + **Examples**: -- `ce-pr-description` → current-branch, no focus +- `ce-pr-description` → current-branch, no focus, auto-detect base - `ce-pr-description emphasize the benchmarks` → current-branch, focus = "emphasize the benchmarks" +- `ce-pr-description base:origin/develop` → current-branch, base pinned to `origin/develop` +- `ce-pr-description base:origin/develop emphasize perf` → same + focus - `ce-pr-description pr:561` → PR #561, no focus - `ce-pr-description #561 do a good job with the perf story` → PR #561, focus = "do a good job with the perf story" - `ce-pr-description https://github.com/foo/bar/pull/561 emphasize safety` → PR #561 in foo/bar, focus = "emphasize safety" @@ -68,10 +72,11 @@ Parse the input (see Inputs above) and branch on which mode it selects. ### Current-branch mode (default when no PR reference was given) -Determine the base against which to compare: +Determine the base against which to compare, in this priority order: -1. Check whether the current branch already has an open PR on this repo. If yes, use that PR's `baseRefName` as the base — this handles the case of a feature branch targeting a non-default base (e.g., `develop`). -2. Otherwise fall back to the repo's default base (`origin/HEAD`). +1. **Caller-supplied `base:<ref>`** — if present, use it verbatim. The caller is asserting the correct base. The ref must resolve locally. +2. **Existing PR's `baseRefName`** — if the current branch already has an open PR on this repo, use that PR's base. Handles feature branches targeting non-default bases (e.g., `develop`) when the PR is already open. +3. **Repo default (`origin/HEAD`)** — fall back for branches with no PR yet and no caller-supplied base. ```bash # Detect current branch (fail if detached HEAD) @@ -81,14 +86,17 @@ if [ -z "$CURRENT_BRANCH" ]; then exit 1 fi -# Prefer an existing PR's base, fall back to origin/HEAD -EXISTING_PR_BASE=$(gh pr view --json baseRefName --jq '.baseRefName' 2>/dev/null) -if [ -n "$EXISTING_PR_BASE" ]; then - BASE_REF="origin/$EXISTING_PR_BASE" +# Priority: caller-supplied base: > existing PR's baseRefName > origin/HEAD +if [ -n "$CALLER_BASE" ]; then + BASE_REF="$CALLER_BASE" else - # Default base from origin/HEAD (the repo's default branch) - BASE_REF=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null) - BASE_REF="${BASE_REF:-origin/main}" + EXISTING_PR_BASE=$(gh pr view --json baseRefName --jq '.baseRefName' 2>/dev/null) + if [ -n "$EXISTING_PR_BASE" ]; then + BASE_REF="origin/$EXISTING_PR_BASE" + else + BASE_REF=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null) + BASE_REF="${BASE_REF:-origin/main}" + fi fi ``` diff --git a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md index e17c20ae3..8c268f006 100644 --- a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md +++ b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md @@ -201,8 +201,8 @@ When evidence is not possible (docs-only, markdown-only, changelog-only, release **Delegate title and body generation to `ce-pr-description`.** Load the `ce-pr-description` skill: -- **For a new PR** (no existing PR found in Step 3): invoke with no PR reference. `ce-pr-description`'s current-branch mode auto-detects `origin/HEAD` (or an existing PR's base, if one somehow appears between Step 3 and now) and describes the commits on HEAD. Any captured-evidence context or user focus is passed as free-text steering (e.g., "include the captured demo: <URL> as a `## Demo` section"). -- **For an existing PR** (found in Step 3): invoke with the full PR URL from the Step 3 context (e.g., `https://github.com/owner/repo/pull/123`). The URL preserves repo/PR identity even when invoked from a worktree or subdirectory. Append any focus steering as free text after the URL. +- **For a new PR** (no existing PR found in Step 3): invoke with `base:<base-remote>/<base-branch>` using the already-resolved base from earlier in this step, so `ce-pr-description` describes the correct commit range even when the branch targets a non-default base (e.g., `develop`, `release/*`). Append any captured-evidence context or user focus as free-text steering (e.g., "include the captured demo: <URL> as a `## Demo` section"). +- **For an existing PR** (found in Step 3): invoke with the full PR URL from the Step 3 context (e.g., `https://github.com/owner/repo/pull/123`). The URL preserves repo/PR identity even when invoked from a worktree or subdirectory; the skill reads the PR's own `baseRefName` so no `base:` override is needed. Append any focus steering as free text after the URL. `ce-pr-description` returns a `{title, body}` block. It applies the value-first writing principles, commit classification, sizing, narrative framing, writing voice, visual communication, numbering rules, and the Compound Engineering badge footer internally. Use the returned values verbatim in Step 7; do not layer manual edits onto them unless a focused adjustment is required (e.g., splicing an evidence block captured in this step that was not passed as steering text). From 90744be48bad7e55576cbefdacb2b44fa65ed50c Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 20:55:52 -0700 Subject: [PATCH 12/14] Address PR review feedback (#561) Branch diff collection now falls back to local state when the remote is unreachable. rev-parse --verify first; only fetch if the base ref isn't local. Restores the pre-refactor behavior and prevents offline, restricted-network, or expired-auth environments from hard-failing when git diff <base>...HEAD could run entirely from local refs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../compound-engineering/skills/git-commit-push-pr/SKILL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md index 8c268f006..f6a93c1b1 100644 --- a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md +++ b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md @@ -182,10 +182,11 @@ The working-tree diff from Step 1 only shows uncommitted changes at invocation t If none resolve, ask the user to specify the target branch. -**Gather the full branch diff (before evidence decision).** The working-tree diff from Step 1 only reflects uncommitted changes at invocation time — on the common "feature branch, all pushed, open PR" path, Step 1 skips the commit/push steps and the working-tree diff is empty. The evidence decision below needs the real branch diff to judge whether behavior is observable, so compute it explicitly against the base resolved above: +**Gather the full branch diff (before evidence decision).** The working-tree diff from Step 1 only reflects uncommitted changes at invocation time — on the common "feature branch, all pushed, open PR" path, Step 1 skips the commit/push steps and the working-tree diff is empty. The evidence decision below needs the real branch diff to judge whether behavior is observable, so compute it explicitly against the base resolved above. Only fetch when the local ref isn't available — if `<base-remote>/<base-branch>` already resolves locally, run the diff from local state so offline / restricted-network / expired-auth environments don't hard-fail: ```bash -git fetch --no-tags <base-remote> <base-branch> +git rev-parse --verify <base-remote>/<base-branch> >/dev/null 2>&1 \ + || git fetch --no-tags <base-remote> <base-branch> git diff <base-remote>/<base-branch>...HEAD ``` From 3c062b0fbca54d22b775991af7ae8f1aeb419330 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 21:12:50 -0700 Subject: [PATCH 13/14] docs(ce-pr-description): add to Git Workflow skill table New entry for ce-pr-description in the README's Git Workflow section. Also updates the git-commit-push-pr entry to note that it delegates title/body generation to ce-pr-description. Skill count bumped from 41+ to 42+. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- plugins/compound-engineering/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/compound-engineering/README.md b/plugins/compound-engineering/README.md index cb23972bd..89c016030 100644 --- a/plugins/compound-engineering/README.md +++ b/plugins/compound-engineering/README.md @@ -11,7 +11,7 @@ After installing, run `/ce-setup` in any project. It diagnoses your environment, | Component | Count | |-----------|-------| | Agents | 50+ | -| Skills | 41+ | +| Skills | 42+ | ## Skills @@ -44,9 +44,10 @@ For `/ce-optimize`, see [`skills/ce-optimize/README.md`](./skills/ce-optimize/RE | Skill | Description | |-------|-------------| +| `ce-pr-description` | Write or regenerate a value-first PR title and body from the current branch or a specified PR; used directly or by other skills | | `git-clean-gone-branches` | Clean up local branches whose remote tracking branch is gone | | `git-commit` | Create a git commit with a value-communicating message | -| `git-commit-push-pr` | Commit, push, and open a PR with an adaptive description; also update an existing PR description | +| `git-commit-push-pr` | Commit, push, and open a PR with an adaptive description; also update an existing PR description (delegates title/body generation to `ce-pr-description`) | | `git-worktree` | Manage Git worktrees for parallel development | ### Workflow Utilities From d0bdc77338f51e749c4cda503a03eba90cd8c647 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 14 Apr 2026 21:21:26 -0700 Subject: [PATCH 14/14] docs(git-commit-push-pr): drop stale focus: keyword references Two evidence-decision bullets still said "pass via focus: context" after the ce-pr-description refactor dropped the formal focus: keyword in favor of free-text steering. Matches the existing wording elsewhere in this file (DU-3 already says "steering text") and matches the ce-pr-description skill's current input contract. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- .../compound-engineering/skills/git-commit-push-pr/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md index f6a93c1b1..8c1cde616 100644 --- a/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md +++ b/plugins/compound-engineering/skills/git-commit-push-pr/SKILL.md @@ -194,8 +194,8 @@ Use this branch diff (not the working-tree diff) for the evidence decision. If t **Evidence decision (before delegation).** If the branch diff changes observable behavior (UI, CLI output, API behavior with runnable code, generated artifacts, workflow output) and evidence is not otherwise blocked (unavailable credentials, paid services, deploy-only infrastructure, hardware), ask: "This PR has observable behavior. Capture evidence for the PR description?" -- **Capture now** -- load the `ce-demo-reel` skill with a target description inferred from the branch diff. ce-demo-reel returns `Tier`, `Description`, and `URL`. Note the captured evidence so it can be passed as `focus:` context to `ce-pr-description` (e.g., "include the captured demo: <URL> as a `## Demo` section") or spliced into the returned body before apply. If capture returns `Tier: skipped` or `URL: "none"`, proceed with no evidence. -- **Use existing evidence** -- ask for the URL or markdown embed, then pass it via `focus:` or splice in before apply. +- **Capture now** -- load the `ce-demo-reel` skill with a target description inferred from the branch diff. ce-demo-reel returns `Tier`, `Description`, and `URL`. Note the captured evidence so it can be passed as free-text steering to `ce-pr-description` (e.g., "include the captured demo: <URL> as a `## Demo` section") or spliced into the returned body before apply. If capture returns `Tier: skipped` or `URL: "none"`, proceed with no evidence. +- **Use existing evidence** -- ask for the URL or markdown embed, then pass it as free-text steering to `ce-pr-description` or splice in before apply. - **Skip** -- proceed with no evidence section. When evidence is not possible (docs-only, markdown-only, changelog-only, release metadata, CI/config-only, test-only, or pure internal refactors), skip without asking.