| allowed-tools | Bash Read Write Edit Grep Glob |
| argument-hint | [--repo owner/repo-or-url-or-remote] [--no-draft] [--wsl] |
| description | Create and publish a GitHub pull request for Slang work, defaulting to the local origin remote and using draft PRs only for shader-slang/* targets unless the user specifies another repository, remote, or draft option. Use for Slang-related PR creation requests, including shader-slang/* targets even when the user does not explicitly name this skill. Handles WSL environments that require Windows-hosted tools unless --wsl is requested. |
| name | slang-pr-create |
| required-capabilities | shell git github-cli file-read |
Slang PR Create
Create a focused GitHub pull request from the current branch. Default to the
repository configured by the local origin remote; if the user specifies a repo
or remote, use that target instead.
Use this skill for Slang-related requests to create, open, publish, or prepare
a PR. It can target any GitHub repository or git remote, defaulting to local
origin; requests targeting a shader-slang/* repository should use this skill
even when the user does not explicitly invoke /slang-pr-create.
Usage: /slang-pr-create [--repo owner/repo-or-url-or-remote] [--no-draft] [--wsl]
PRs targeting shader-slang/* are created as drafts by default. PRs targeting
other repositories are created ready for review. Use --no-draft only when a
shader-slang/* PR should be ready for review immediately. Created PRs are
assigned to @me by default.
Only pass labels that are present in the target repository. If the target
repository has a CoPilot label, add it when creating the PR.
After creating a draft PR, request CodeRabbit review by commenting
@coderabbitai review.
--wsl means "use native WSL tools" when running inside WSL. Without it,
require Windows-hosted .exe tools such as gh.exe. If they are missing, stop
and tell the user to install the Windows tools or rerun with --wsl.
Preconditions
- GitHub CLI (
gh or gh.exe) is installed and authenticated.
- The current directory is a git worktree for the branch to publish.
- The branch has committed changes intended for the PR.
Select Tools
Before running any git or gh command, detect WSL and choose tools. When the
agent is running under WSL, require .exe tools by default because GitHub
authentication and browser login are often configured on the Windows side. Do
not silently fall back to native WSL tools in this mode. If the user passes
--wsl, explicitly use native WSL tools instead.
ARGS="${ARGUMENTS:-}"
USE_WSL_TOOLS=false
DRAFT=true
if printf '%s\n' "$ARGS" | grep -Eq '(^|[[:space:]])--wsl([[:space:]]|$)'; then
USE_WSL_TOOLS=true
ARGS="$(printf '%s\n' "$ARGS" | sed -E 's/(^|[[:space:]])--wsl([[:space:]]|$)/ /; s/^[[:space:]]+//; s/[[:space:]]+$//')"
fi
if printf '%s\n' "$ARGS" | grep -Eq '(^|[[:space:]])--no-draft([[:space:]]|$)'; then
DRAFT=false
ARGS="$(printf '%s\n' "$ARGS" | sed -E 's/(^|[[:space:]])--no-draft([[:space:]]|$)/ /; s/^[[:space:]]+//; s/[[:space:]]+$//')"
fi
is_wsl() {
[ -n "${WSL_DISTRO_NAME:-}" ] || grep -qi microsoft /proc/version 2>/dev/null
}
choose_tool() {
tool="$1"
if is_wsl && [ "$USE_WSL_TOOLS" = false ]; then
if command -v "${tool}.exe" >/dev/null 2>&1; then
printf '%s.exe\n' ""
0
>&2
>&2
1
-v >/dev/null 2>&1;
0
>&2
1
}
GIT= || 1
GH= || 1
Use $GIT and $GH in all subsequent shell examples. Strip \r from command
substitution output because Windows .exe tools may print CRLF:
clean_line() { tr -d '\r'; }
Write the PR body outside the tracked worktree, then pass a Windows path from
wslpath -w when $GH ends in .exe:
BODY_FILE="$("$GIT" rev-parse --git-path slang-pr-body.md | clean_line)"
if is_wsl && command -v wslpath >/dev/null 2>&1; then
case "$BODY_FILE" in
*\\*|[A-Za-z]:*) BODY_FILE="$(wslpath -u "$BODY_FILE")" ;;
esac
fi
BODY_FILE_ARG="$BODY_FILE"
if is_wsl && [ "${GH%.exe}" != "$GH" ] && command -v wslpath >/dev/null 2>&1; then
BODY_FILE_ARG="$(wslpath -w "$BODY_FILE")"
fi
Resolve Inputs
Use the repository from the user request when provided. Accept --repo owner/repo, --repo https://github.com/owner/repo, --repo <git-remote>, a
bare positional owner/repo, a GitHub URL, or a bare positional git remote name
such as origin or upstream.
If a bare token with no slash matches git remote, treat that remote's URL as
the target repository and prefer that same remote as the push remote. If the bare
token is upstream but no upstream git remote exists, infer
shader-slang/<repo-name> from the local origin repository name, so forks of
repositories such as shader-slang/slangpy target the matching upstream
repository. If omitted, behave as if origin was provided. Do not default to a
specific shader-slang/* repository when any explicit repository or remote
argument was provided; fail instead if it cannot be resolved.
set -- $ARGS
TARGET_ARG=""
while [ $# -gt 0 ]; do
case "$1" in
--repo)
shift
if [ $# -eq 0 ] || [ -z "${1:-}" ] || [[ "${1:-}" == --* ]]; then
echo "Missing value for --repo"
exit 1
fi
TARGET_ARG="${1:-}"
;;
--repo=*)
if [ -z "${1#--repo=}" ]; then
echo "Missing value for --repo"
exit 1
fi
TARGET_ARG="${1#--repo=}"
;;
*)
if [ -z "$TARGET_ARG" ]; then
TARGET_ARG="$1"
fi
;;
esac
shift
done
if [ -z "$TARGET_ARG" ]; then
TARGET_ARG="origin"
fi
TARGET_REMOTE=
[[ != */* && != http://* && != https://* && != git@github.com:* ]];
remote get-url >/dev/null 2>&1;
TARGET_REMOTE=
TARGET_URL=
REPO=
[ = ];
ORIGIN_URL=
[ -z ];
1
ORIGIN_REPO_NAME=
ORIGIN_REPO_NAME=
ORIGIN_REPO_NAME=
[ -z ];
1
REPO=
1
REPO=
REPO=
Always query the target repository's default branch instead of assuming master
or main:
REPO_NAME_WITH_OWNER="$("$GH" repo view "$REPO" --json nameWithOwner --jq .nameWithOwner | clean_line)"
BASE="$("$GH" repo view "$REPO" --json defaultBranchRef --jq .defaultBranchRef.name | clean_line)"
BRANCH="$("$GIT" branch --show-current | clean_line)"
case "$REPO_NAME_WITH_OWNER" in
shader-slang/*)
;;
*)
DRAFT=false
;;
esac
Try to determine the issue references that the PR is intended to fix before
creating the PR. Closing references should use Fixes #123 for same-repository
issues and Fixes owner/repo#123 for cross-repository issues.
First ask GitHub whether the current branch is already linked to one or more
issue development branches. gh issue develop --list works once an issue
number is known; to discover issues from the current branch, query the linked
branch metadata:
OWNER="${REPO_NAME_WITH_OWNER%/*}"
NAME="${REPO_NAME_WITH_OWNER#*/}"
mapfile -t LINKED_ISSUE_REFS < <("$GH" api graphql --paginate --slurp \
-F owner="$OWNER" \
-F name="$NAME" \
-f query='query($owner: String!, $name: String!, $endCursor: String) {
repository(owner: $owner, name: $name) {
issues(first: 100, states: OPEN, after: $endCursor) {
nodes {
number
repository { nameWithOwner }
linkedBranches(first: 20) {
nodes { ref { name repository { nameWithOwner } } }
}
}
pageInfo { hasNextPage endCursor }
}
}
}' | jq -r --arg branch "$BRANCH" '
[.[].data.repository.issues.nodes[]
| select(any(.linkedBranches.nodes[]?; .ref.name == $branch))
| "\(.repository.nameWithOwner)#\(.number)"]
| unique
| .[]
')
This uses gh api --slurp with --paginate so gh wraps paginated GraphQL
responses in one JSON array before jq processes them.
If jq is unavailable, run the same GraphQL query and inspect the JSON output
manually for linkedBranches entries whose ref.name equals $BRANCH.
If this returns one or more issue references, include all of the references
that the PR fixes. If it returns none, use any full issue references explicitly
provided by the user. If only issue numbers are provided or inferred from clear
local evidence such as the branch name, commit message, or existing task
context, combine each issue number with $REPO_NAME_WITH_OWNER. If any issue
belongs to a different repository, use that issue's owner/repo instead.
Do not stop PR creation only because issue references cannot be inferred. Use
only confidently determined issue references, skip ambiguous or unavailable
ones, and omit closing lines entirely if no issue reference is known.
PowerShell / gh.exe equivalent:
This snippet assumes $repo has already been resolved and normalized using the
same input-resolution rules described above. Do not use the placeholder value
literally.
$repo = "<resolved-target-repo>"
$repoNameWithOwner = gh.exe repo view $repo --json nameWithOwner --jq ".nameWithOwner"
$base = gh.exe repo view $repo --json defaultBranchRef --jq ".defaultBranchRef.name"
$branch = git branch --show-current
$owner, $name = $repoNameWithOwner -split '/', 2
$query = @'
query($owner: String!, $name: String!, $endCursor: String) {
repository(owner: $owner, name: $name) {
issues(first: 100, states: OPEN, after: $endCursor) {
nodes {
number
repository { nameWithOwner }
linkedBranches(first: 20) {
nodes { ref { name repository { nameWithOwner } } }
}
}
pageInfo { hasNextPage endCursor }
}
}
}
'@
$pages = gh.exe api graphql --paginate --slurp `
-F "owner=$owner" `
-F "name=$name" `
-f "query=$query" | ConvertFrom-Json
$linkedIssueRefs = @()
foreach ($page in $pages) {
foreach ($issue in $page.data.repository.issues.nodes) {
if ($issue.linkedBranches.nodes | Where-Object { $_.ref -and $_.ref.name -eq $branch }) {
$linkedIssueRefs += "$($issue.repository.nameWithOwner)#$($issue.number)"
}
}
}
$linkedIssueRefs = $linkedIssueRefs | Sort-Object -Unique
Required Safety Check
Before pushing or creating a PR, run:
"$GIT" status --short
If there is any output, stop and clarify with the user before continuing.
Show the changed and untracked files, then ask how to proceed. Do not commit,
stash, discard, push, or create the PR until the user chooses.
Offer these options:
- Commit all changes: ask for a commit message, then run
"$GIT" add -A && "$GIT" commit -m "<message>".
- Commit only staged changes: only if staged files exist; ask for a commit
message, then run
"$GIT" commit -m "<message>".
- Stash changes: run
"$GIT" stash push -m "slang-pr-create stash" and
create the PR from the current committed HEAD.
- Abort: stop so the user can handle the worktree manually.
Create The PR
Check basic state:
"$GH" auth status
"$GIT" status --short
"$GIT" remote -v
"$GIT" branch --show-current
"$GH" repo view "$REPO" --json nameWithOwner,defaultBranchRef,url
Branch Naming
Preserve the current branch whenever possible. Do not rename the current
branch or move the commits onto a new branch when the current branch is already
a topic branch (any branch whose name is not the target default branch). In that
case keep BRANCH as-is and create the PR from it.
Only create a new branch when the current branch is the default branch, i.e.
BRANCH is empty (detached HEAD) or BRANCH equals BASE. Never open a PR
whose head is the default branch. When this happens, choose a new branch name
that reflects what the committed changes do (for example, derived from the
primary commit subject), switch to it, and create the PR from that branch:
if [ -z "$BRANCH" ] || [ "$BRANCH" = "$BASE" ]; then
COMMIT_SUBJECT="$("$GIT" log -1 --pretty=%s | clean_line)"
NEW_BRANCH="$(printf '%s' "$COMMIT_SUBJECT" \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' \
| cut -c1-50 \
| sed -E 's/-+$//')"
[ -z "$NEW_BRANCH" ] && NEW_BRANCH="slang-pr-$("$GIT" rev-parse --short HEAD | clean_line)"
CANDIDATE="$NEW_BRANCH"
suffix=1
while "$GIT" show-ref --verify --quiet "refs/heads/$CANDIDATE"; do
suffix=$((suffix + 1))
CANDIDATE="${NEW_BRANCH}-${suffix}"
done
NEW_BRANCH="$CANDIDATE"
"$GIT" switch -c "$NEW_BRANCH" || exit 1
BRANCH="$NEW_BRANCH"
>&2
Prefer a descriptive slug, but you may instead ask the user for a branch name
when the change spans many commits or no single commit subject captures it.
After this step BRANCH always names a topic branch distinct from BASE.
Fetch the target default branch and verify the branch has commits for the PR:
"$GIT" fetch "https://github.com/$REPO.git" "$BASE"
"$GIT" log --oneline FETCH_HEAD..HEAD
If there are no commits ahead of the target default branch, stop and report that
there is nothing committed to open as a PR.
Push the branch if needed. Track the remote branch that was actually published
and use it later for gh pr create:
If the same-name push is rejected because the remote branch already exists or
has diverged, push HEAD to a new remote branch name and create the PR from that
new branch. For authentication, permission, or missing-remote failures, stop
instead of trying a new branch name.
if [ -n "${TARGET_REMOTE:-}" ]; then
PUSH_REMOTE="$TARGET_REMOTE"
else
PUSH_REMOTE="$("$GIT" config --get "branch.$BRANCH.remote" | clean_line || true)"
fi
if [ -z "$PUSH_REMOTE" ]; then
while IFS= read -r remote; do
[ -z "$remote" ] && continue
if "$GIT" remote get-url --push "$remote" >/dev/null 2>&1; then
PUSH_REMOTE="$remote"
break
fi
done < <("$GIT" remote | clean_line)
fi
if [ -z "$PUSH_REMOTE" ]; then
echo "Could not determine a push remote. Ask before adding a remote or changing push destinations."
exit 1
fi
PUBLISHED_BRANCH="$BRANCH"
PR_HEAD="$PUBLISHED_BRANCH"
PUSH_LOG="$(mktemp "${TMPDIR:-/tmp}/slang-pr-push.XXXXXX")"
EXIT
! push -u 2>;
PUSH_OUTPUT=
| grep -Eiq ;
SHORT_HEAD=
PUBLISHED_BRANCH=
suffix=1
ls-remote --exit-code --heads >/dev/null 2>&1;
suffix=$((suffix + ))
PUBLISHED_BRANCH=
>&2
>&2
push -u || 1
PR_HEAD=
>&2
1
Prepare a concise PR body in $BODY_FILE. Use this structure, omitting any
section that is not applicable to the PR. Replace placeholder guidance with real
content for sections that remain; do not leave empty headings or [[...]]
placeholder text in the final PR description.
[[Add one line per confirmed issue, e.g., `Fixes #123` or `Fixes owner/repo#123`.
Omit this line when no issue reference is known.]]
## Motivation
[[The problem, grounded in a concrete example or motivating test case. Write for
a reviewer without the full context in their head.]]
## Proposed solution
[[The approach taken and why it is principled rather than a minimal-edit-distance
workaround.]]
## Change summary
[[The files/areas touched and what each one does. Wire claims to the source with
function names and `file.cpp:line` references.]]
## Concepts and vocabulary
[[A short glossary that restates only the codebase-specific or subtle terms the
process report relies on (e.g., witness, facet, the fixpoint solver, a non-obvious
distinction the fix hinges on). Do not explain basic, well-known concepts such as
interface or associated type — assume them. Omit this section if no such terms
are needed.]]
## Process report
[[Explain every change with a logical reason. For a change addressing a cascading
issue, describe the issue (with its motivating test case) and justify the fix with
a code trace — the exact functions/insts involved — explaining why it is necessary
and principled rather than a workaround. For any change that handles, guards, or
special-cases a particular input shape, answer the input-shape check: is that shape
itself correct and principled, or should its producer have been fixed instead? — so
a reviewer can confirm the fix sits at the right layer.]]
Ground each abstract claim in a concrete example, and wire explanations to the
source (function name and file, or file.cpp:line). Do not include validation
logs or a ## Test Plan section in the PR description.
If the PR is intentionally backward-compatibility breaking, include this section
in the PR body:
## Breaking change
1. Existing systems and shaders may ...
2. Work around or resolve this by ...
The ## Breaking change section is required when the PR is intentionally
backward-compatibility breaking or has the pr: breaking label. It must explain
both the problems the PR may cause for existing systems and shaders, and how
users can work around or properly resolve those problems.
When one or more fixed issue references are known, put one Fixes #123 line per
fixed issue at the top of the PR body. Use the target repository's local issue
number form for same-repository issues, and use Fixes owner/repo#123 only for
cross-repository issues. Do not duplicate issue references, invent issue
references, or include placeholder closing text. If no issue reference is known,
omit Fixes lines.
Use at most one compatibility label: pr: non-breaking or pr: breaking. These
labels cannot coexist. Use pr: breaking only when the change is clearly
backward-compatibility breaking. Use pr: non-breaking only when the user says
the PR is non-breaking or the non-breaking classification is otherwise clear.
When the compatibility classification is unclear, do not guess and do not apply
either label. Compatibility labels are optional metadata; if the target
repository does not have the selected label, continue creating the PR without
that label instead of creating labels or blocking PR creation.
Only pass labels that exist in the target repository. This applies to
compatibility labels such as pr: non-breaking or pr: breaking, and to
integration labels such as CoPilot.
Create the PR:
LABEL_ARGS=()
LABEL_NAMES="$("$GH" label list \
--repo "$REPO" \
--limit 1000 \
--json name \
--jq '.[].name' 2>/dev/null | clean_line || true)"
repo_has_label() {
label_name="$1"
printf '%s\n' "$LABEL_NAMES" | grep -Fxq "$label_name"
}
add_label_if_available() {
label_name="$1"
if repo_has_label "$label_name"; then
LABEL_ARGS+=(--label "$label_name")
else
printf 'Target repository is missing optional label: %s\n' "$label_name" >&2
printf 'Creating the PR without that label.\n' >&2
fi
}
BREAKING_CHANGE=false
NON_BREAKING_CHANGE=false
COMPAT_LABEL=""
if [ "$BREAKING_CHANGE" = true ]; then
COMPAT_LABEL=
BODY_FILE_READ=
is_wsl && -v wslpath >/dev/null 2>&1;
*\\*|[A-Za-z]:*) BODY_FILE_READ= ;;
BREAKING_HEADING_REGEX=
! grep -Eq ;
1
[ = ];
COMPAT_LABEL=
[ -n ];
add_label_if_available
add_label_if_available
DRAFT_ARGS=()
[ = ];
DRAFT_ARGS=(--draft)
() {
pr_ref=
[ = ];
comment --body
}
PR_URL= || 1
PR_URL=
request_coderabbit_review_if_draft
Keep --assignee @me in the command unless the user explicitly requests a
different assignee.
If the branch was pushed to a fork rather than the target repository, use
--head "<user>:<branch>" with the published branch name from PR_HEAD.
Determine the fork owner from the push remote:
PUSH_URL="$("$GIT" remote get-url --push "$PUSH_REMOTE" | clean_line)"
HEAD_REPO="$("$GH" repo view "$PUSH_URL" --json nameWithOwner --jq .nameWithOwner | clean_line)"
HEAD_OWNER="${HEAD_REPO%%/*}"
PR_URL="$("$GH" pr create \
--repo "$REPO" \
--base "$BASE" \
--head "$HEAD_OWNER:$PR_HEAD" \
--title "<title>" \
--body-file "$BODY_FILE_ARG" \
--assignee @me \
"${DRAFT_ARGS[@]}" \
"${LABEL_ARGS[@]}")" || exit 1
PR_URL="$(printf '%s\n' "$PR_URL" | clean_line)"
request_coderabbit_review_if_draft "$PR_URL"
printf '%s\n' "$PR_URL"
For Windows PowerShell:
$GH = "gh.exe"
# Branch naming: preserve the current topic branch. Only when on the default
# branch (or detached HEAD) create a new topic branch named from the latest
# commit subject, then switch to it. Mirrors the Bash "Branch Naming" step.
if ([string]::IsNullOrEmpty($branch) -or $branch -eq $base) {
$commitSubject = (git.exe log -1 --pretty=%s).Trim()
$newBranch = $commitSubject.ToLower() -replace '[^a-z0-9]+', '-' -replace '^-+', '' -replace '-+$', ''
if ($newBranch.Length -gt 50) {
$newBranch = $newBranch.Substring(0, 50) -replace '-+$', ''
}
if ([string]::IsNullOrEmpty($newBranch)) {
$shortHead = (git.exe rev-parse --short HEAD).Trim()
$newBranch = "slang-pr-$shortHead"
}
$candidate = $newBranch
$suffix = 1
while (git.exe show-ref --verify --quiet "refs/heads/$candidate") {
$suffix++
$candidate = "${newBranch}-${suffix}"
}
$newBranch = $candidate
git.exe switch -c $newBranch
if ($LASTEXITCODE -ne 0) { exit 1 }
$branch = $newBranch
[Console]::Error.WriteLine("Was on default branch $base; created topic branch $branch for the PR.")
}
$headBranch = $branch
$repoNameWithOwner = $repo -replace '^https://github\.com/', '' -replace '^git@github\.com:', '' -replace '\.git$', ''
$bodyFile = (git.exe rev-parse --git-path slang-pr-body.md).Trim()
$breakingChange = $false
$nonBreakingChange = $false
# Set $breakingChange = $true only when the user explicitly says the PR is breaking
# or the change is intentionally backward-compatibility breaking.
# Set $nonBreakingChange = $true only when the user says the PR is non-breaking
# or the non-breaking classification is otherwise clear.
$compatLabel = $null
if ($breakingChange) {
$compatLabel = "pr: breaking"
} elseif ($nonBreakingChange) {
$compatLabel = "pr: non-breaking"
}
$hasBreakingChangeSection = Select-String -LiteralPath $bodyFile -SimpleMatch "## Breaking change" -Quiet
if ($breakingChange -and -not $hasBreakingChangeSection) {
throw "Breaking PRs must include a '## Breaking change' section in the PR body."
}
$labelArgs = @()
$labelNames = @(& $GH label list `
--repo $repoNameWithOwner `
--limit 1000 `
--json name `
--jq ".[].name" 2>$null)
function Add-LabelIfAvailable {
param([string]$LabelName)
if ($labelNames | Where-Object { $_ -ceq $LabelName }) {
$script:labelArgs += @("--label", $LabelName)
} else {
Write-Warning "Target repository is missing optional label: $LabelName"
Write-Warning "Creating the PR without that label."
}
}
if ($compatLabel) {
Add-LabelIfAvailable $compatLabel
}
Add-LabelIfAvailable "CoPilot"
$prCreateArgs = @(
"pr", "create",
"--repo", $repo,
"--base", $base,
"--head", $headBranch,
"--title", "PR title",
"--body-file", $bodyFile,
"--assignee", "@me"
)
if ($repoNameWithOwner -like "shader-slang/*") {
$prCreateArgs += "--draft"
}
$prCreateArgs += $labelArgs
$prUrl = & $GH @prCreateArgs
if ($repoNameWithOwner -like "shader-slang/*") {
& $GH pr comment $prUrl --body "@coderabbitai review"
}
$prUrl
Omit --draft for non-shader-slang/* targets. Also omit it if the user passes
--no-draft or explicitly requests a shader-slang/* PR that is ready for
review.
After Creation
Report the PR URL, the base branch, the published head branch, whether the push
fell back to a new remote branch name, which labels were applied, whether a
CodeRabbit review request was posted, and whether any validation was run. If PR
creation fails because the branch was not pushed to a usable remote or the
target repo differs from the local origin, explain the failure and ask before
adding remotes or changing push destinations.