mirror of
https://github.com/EveryInc/compound-engineering-plugin.git
synced 2026-06-19 15:41:46 +02:00
refactor(ce-pr-description, git-commit-push-pr): hand off PR body via temp file
Change ce-pr-description's output contract from {title, body} to
{title, body_file}. The body is written to an OS temp file via
mktemp + heredoc and the caller substitutes it back with
"$(cat "$body_file")". This avoids emitting the body text twice
(once as the skill's output, again when the caller formats the
gh pr command), which was wasteful on large PR descriptions.
Also add a narrow AGENTS.md exception for value-producing
preparatory commands (VAR=$(cmd1) && cmd2 "$VAR"), so the skill
can use the natural mktemp + heredoc pattern without manually
threading the path across two bash calls.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -136,7 +136,7 @@ Why: shell-heavy exploration causes avoidable permission prompts in sub-agent wo
|
||||
|
||||
- [ ] Never instruct agents to use `find`, `ls`, `cat`, `head`, `tail`, `grep`, `rg`, `wc`, or `tree` through a shell for routine file discovery, content search, or file reading
|
||||
- [ ] Describe tools by capability class with platform hints — e.g., "Use the native file-search/glob tool (e.g., Glob in Claude Code)" — not by Claude Code-specific tool names alone
|
||||
- [ ] When shell is the only option (e.g., `ast-grep`, `bundle show`, git commands), instruct one simple command at a time — no action chaining (`cmd1 && cmd2`, `cmd1 ; cmd2`) and no error suppression (`2>/dev/null`, `|| true`). Boolean conditions within if/while guards (`[ -n "$X" ] || [ -n "$Y" ]`) are fine — that is normal conditional logic, not action chaining. Simple pipes (e.g., `| jq .field`) and output redirection (e.g., `> file`) are acceptable when they don't obscure failures
|
||||
- [ ] When shell is the only option (e.g., `ast-grep`, `bundle show`, git commands), instruct one simple command at a time — no action chaining (`cmd1 && cmd2`, `cmd1 ; cmd2`) and no error suppression (`2>/dev/null`, `|| true`). Two narrow exceptions: boolean conditions within if/while guards (`[ -n "$X" ] || [ -n "$Y" ]`) are fine — that is normal conditional logic, not action chaining. **Value-producing preparatory commands** (`VAR=$(cmd1) && cmd2 "$VAR"`) are also fine when `cmd2` strictly consumes `cmd1`'s output and splitting would require manually threading the value through model context across bash calls (e.g., `BODY_FILE=$(mktemp -u) && cat > "$BODY_FILE" <<EOF ... EOF`). Simple pipes (e.g., `| jq .field`) and output redirection (e.g., `> file`) are acceptable when they don't obscure failures
|
||||
- [ ] **Pre-resolution exception:** `!` backtick pre-resolution commands run at skill load time, not at agent runtime. They may use chaining (`&&`, `||`), error suppression (`2>/dev/null`), and fallback sentinels (e.g., `|| echo '__NO_CONFIG__'`) to produce a clean, parseable value for the model. This is the preferred pattern for environment probes (CLI availability, config file reads) that would otherwise require runtime shell calls with chaining. Example: `` !`command -v codex >/dev/null 2>&1 && echo "AVAILABLE" || echo "NOT_FOUND"` ``
|
||||
- [ ] Do not encode shell recipes for routine exploration when native tools can do the job; encode intent and preferred tool classes instead
|
||||
- [ ] For shell-only workflows (e.g., `gh`, `git`, `bundle show`, project CLIs), explicit command examples are acceptable when they are simple, task-scoped, and not chained together
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
name: ce-pr-description
|
||||
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."
|
||||
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_file} (body written to an OS temp file) 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 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.
|
||||
Generate a conventional-commit-style title and a value-first body describing a pull request's work. Returns structured `{title, body_file}` for the caller to apply — this skill never invokes `gh pr edit` or `gh pr create`, and never prompts for interactive confirmation. The body is written to an OS temp file (via `mktemp`) rather than emitted inline, so callers can pass it to `gh pr edit/create` via `cat` substitution without the body being tokenized twice (once in the skill's return, once in the caller's heredoc).
|
||||
|
||||
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.
|
||||
|
||||
@@ -49,9 +49,9 @@ Steering text is always optional. If present, incorporate it alongside the diff-
|
||||
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.
|
||||
- **`body_file`** -- absolute path to an OS temp file (created via `mktemp`) containing the body markdown that follows the writing principles below. Do not emit the body inline in the return.
|
||||
|
||||
The caller decides whether to apply via `gh pr edit`, `gh pr create`, or discard. This skill does NOT call those commands itself.
|
||||
The caller decides whether to apply via `gh pr edit`, `gh pr create`, or discard, reading the body from `body_file` (e.g., `--body "$(cat "$BODY_FILE")"`). This skill does NOT call those commands itself. No cleanup is required — `mktemp` files live in OS temp storage, which the OS reaps on its own schedule.
|
||||
|
||||
---
|
||||
|
||||
@@ -122,7 +122,7 @@ gh pr view <pr-ref> --json number,state,title,body,baseRefName,baseRefOid,headRe
|
||||
|
||||
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.
|
||||
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_file}` 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.
|
||||
|
||||
@@ -344,9 +344,26 @@ Assemble the body in this order:
|
||||
|
||||
---
|
||||
|
||||
## Step 9: Return `{title, body}`
|
||||
## Step 9: Return `{title, body_file}`
|
||||
|
||||
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.
|
||||
Write the composed body to an OS temp file, then return the title and the file path 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.
|
||||
|
||||
Generate a unique temp path and write the body to it in a single bash call, then print the path so the caller can capture it:
|
||||
|
||||
```bash
|
||||
BODY_FILE=$(mktemp -u -t ce-pr-body) && cat > "$BODY_FILE" <<'__CE_PR_BODY_END__'
|
||||
<the composed body markdown goes here, verbatim>
|
||||
__CE_PR_BODY_END__
|
||||
echo "$BODY_FILE"
|
||||
```
|
||||
|
||||
This is the value-producing preparatory pattern that AGENTS.md explicitly permits: `cat` strictly consumes the path `mktemp -u` just produced, and splitting would require manually threading the path through two bash calls. The final `echo "$BODY_FILE"` surfaces the path to the caller as the bash tool's stdout.
|
||||
|
||||
Design notes:
|
||||
|
||||
- `mktemp -u -t ce-pr-body` prints a unique path like `/tmp/ce-pr-body.abc123` (Linux) or `/var/folders/.../T/ce-pr-body.abc123` (macOS) without creating the file. The `-u` flag ("unsafe, don't create") is used because the heredoc write creates the file itself — creating it twice is pointless, and some sandboxes (Claude Code's `Write` tool among them) refuse to write to files they haven't `Read` first. This form is portable across macOS (BSD) and Linux (GNU) and avoids the macOS quirk where mid-template `XXXXXX` doesn't substitute. No `.md` suffix is needed — `gh pr edit --body "$(cat <path>)"` reads the bytes regardless of extension.
|
||||
- The sentinel marker `__CE_PR_BODY_END__` is used verbatim — not `EOF`, because PR bodies can legitimately contain literal `EOF` in code blocks and collide with the heredoc terminator. Quoting the sentinel (`<<'__CE_PR_BODY_END__'`) disables shell expansion inside the heredoc, so `$VAR`, backticks, and `${...}` in the body stay literal.
|
||||
- Why not use the native file-write tool (e.g., `Write` in Claude Code): the tool commonly refuses to overwrite a file the session hasn't `Read` yet and is often sandboxed to the workspace directory, so it can't write to `/tmp`. A bash heredoc write sidesteps both constraints. The body is emitted as output tokens exactly once (in the bash command parameter) — identical cost to a Write tool call. The savings versus the old flow come from not re-emitting the body again in the caller's `gh pr edit/create` invocation.
|
||||
|
||||
Format the return as a clearly labeled block so the caller can extract cleanly:
|
||||
|
||||
@@ -354,11 +371,13 @@ Format the return as a clearly labeled block so the caller can extract cleanly:
|
||||
=== TITLE ===
|
||||
<title line>
|
||||
|
||||
=== BODY ===
|
||||
<body markdown>
|
||||
=== BODY_FILE ===
|
||||
<absolute path to the mktemp body file>
|
||||
```
|
||||
|
||||
If Step 1 exited gracefully (closed/merged PR, invalid range, empty commit list), return no title or body — just the reason string.
|
||||
Do not emit the body markdown itself in the return block. The caller reads it from `BODY_FILE` via `cat` substitution inside its `gh pr edit`/`gh pr create` invocation. This avoids tokenizing the body twice (once in the return, once in the caller's tool call) and sidesteps heredoc-escaping bugs when the body contains shell metacharacters.
|
||||
|
||||
If Step 1 exited gracefully (closed/merged PR, invalid range, empty commit list), do not create a body file — just return the reason string.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -69,24 +69,21 @@ 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 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.
|
||||
**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_file}` block without applying or prompting — the body is in an OS temp file rather than inline, to avoid tokenizing it twice.
|
||||
|
||||
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 `ce-pr-description` returns a "not open" or other graceful-exit message instead of a `{title, body_file}` 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 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.
|
||||
**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. Summarize from the body already in conversation context (from the file-write tool call that produced `body_file`); do not re-read the temp file, which would re-emit the body and defeat the handoff's token savings.
|
||||
|
||||
- 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:
|
||||
If confirmed, apply with the returned title and body file:
|
||||
|
||||
```bash
|
||||
gh pr edit --title "<returned title>" --body "$(cat <<'EOF'
|
||||
<returned body>
|
||||
EOF
|
||||
)"
|
||||
gh pr edit --title "<returned title>" --body "$(cat "<returned body_file>")"
|
||||
```
|
||||
|
||||
Report the PR URL.
|
||||
@@ -137,7 +134,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 -- 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.
|
||||
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_file}` that Step 6 produces. Otherwise (no open PR), continue through Steps 6, 7, and 8 in order.
|
||||
|
||||
### Step 4: Branch, stage, and commit
|
||||
|
||||
@@ -205,21 +202,18 @@ When evidence is not possible (docs-only, markdown-only, changelog-only, release
|
||||
- **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).
|
||||
`ce-pr-description` returns a `{title, body_file}` block — the body lives in an OS temp file rather than inline to avoid double-tokenizing it. 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 — in that case, edit the body file directly before applying).
|
||||
|
||||
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.
|
||||
If `ce-pr-description` returns a graceful-exit message instead of `{title, body_file}` (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
|
||||
|
||||
#### New PR (no existing PR from Step 3)
|
||||
|
||||
Using the `{title, body}` returned by `ce-pr-description`:
|
||||
Using the `{title, body_file}` returned by `ce-pr-description`:
|
||||
|
||||
```bash
|
||||
gh pr create --title "<returned title>" --body "$(cat <<'EOF'
|
||||
<returned body>
|
||||
EOF
|
||||
)"
|
||||
gh pr create --title "<returned title>" --body "$(cat "<returned body_file>")"
|
||||
```
|
||||
|
||||
Keep the title under 72 characters; `ce-pr-description` already emits a conventional-commit title in that range.
|
||||
@@ -228,13 +222,10 @@ 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**, 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:
|
||||
- If **yes**, run Step 6 now to generate `{title, body_file}` via `ce-pr-description` (passing the existing PR URL as `pr:`), then apply the returned title and body file:
|
||||
|
||||
```bash
|
||||
gh pr edit --title "<returned title>" --body "$(cat <<'EOF'
|
||||
<returned body>
|
||||
EOF
|
||||
)"
|
||||
gh pr edit --title "<returned title>" --body "$(cat "<returned body_file>")"
|
||||
```
|
||||
|
||||
- If **no** -- skip Step 6 entirely and finish. Do not run delegation or evidence capture when the user declined the rewrite.
|
||||
|
||||
Reference in New Issue
Block a user