with one click
sf-ticket-to-pr
// Turns a GitHub issue into a tested pull request — or comments on the issue explaining why it cannot be done autonomously
// Turns a GitHub issue into a tested pull request — or comments on the issue explaining why it cannot be done autonomously
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | sf-ticket-to-pr |
| description | Turn human words (a GitHub issue or a reviewer comment) into a tested, committed, and PR'd code change |
Human words come in (a freshly opened issue, or a reviewer's comment on an existing PR). You turn them into committed code on a branch — opening a PR if none exists yet, otherwise pushing to the existing one.
You are done when either:
gh pr create ran if
the PR didn't exist before), orfix/issue-<N> branch off maingit push and gh pr create are attributed to claude-bot[bot].Before touching any code, post a triage comment and apply one of three labels. Pick exactly one exit: Acknowledge, Reject, or Split. Then either continue (Acknowledge only) or stop.
Answer these to yourself before picking Acknowledge:
If two or more answers land in "borderline / yes", take the Split exit.
The issue is clear, in-scope, and finishable in one run.
gh issue comment <N> --body "I can do this in one run. Plan:
- <bullet 1 — name a concrete class>
- <bullet 2>
- <bullet 3>
Starting now."
gh issue edit <N> --add-label ai-acknowledged
Then continue to Step 2.
Any of these is true:
Schema changes needed (fields, objects, relationships)
Flows, Permission Sets, Custom Metadata, or anything in unpackaged/
Repro steps / feedback too vague to act on
Cross-component architectural decision required
Data Cloud, External Services, Named Credentials, or config//sfdx-project.json changes
The feedback contradicts the original issue or the existing change
gh issue comment --body "I can't do this. Reason: <one of the conditions above, named explicitly>. Missing: ." gh issue edit --add-label ai-rejected
Stop. Do not proceed to Step 2.
The story is implementable but too big for one run (self-check failed).
gh issue comment <N> --body "This is bigger than one run. Proposed split:
- <sub-issue 1, scoped to ~1 class + tests>
- <sub-issue 2>
Stopping; please open the sub-issues and re-delegate."
gh issue edit <N> --add-label ai-needs-split
Stop. Do not proceed to Step 2.
Same three exits, substitute gh pr comment <PR> and gh pr edit <PR> --add-label.
The label names are the same.
Touch whatever the change actually requires — classes, LWC, agent metadata
(genAiFunctions/, genAiPlugins/, genAiPromptTemplates/), permission sets,
Testing Center XML, anything else under force-app/. The triage in Step 1
is what protects against scope creep, not a folder allowlist.
Apex / coding rules live in CLAUDE.md and rules/salesforce/coding-standards.md — follow them.
For deploys that touch Agentforce metadata (genAiFunctions/, genAiPromptTemplates/, genAiPlugins/, genAiPlannerBundles/, bots/), follow .claude/skills/agentforce-deploy/SKILL.md. Salesforce CLI has known gaps for these — schema-only edits ship nothing, plugin edits don't propagate, active-bot deploys fail. The skill encodes the workarounds. Do not retry the same sf project deploy start command in a loop hoping it lands — read the skill and apply the right fixup.
Read each file you will change in full before editing. Then write the change and update or add the test class.
Redeploy the changed file (the script already deployed everything else):
sed -i 's/aquiva_os__//g' force-app/main/default/classes/<File>.cls && \
sf project deploy start --source-dir force-app/main/default/classes/<File>.cls --concise; \
git checkout -- force-app/main/default/classes/<File>.cls
Run tests:
sf apex run test --test-level RunLocalTests --wait 30 --result-format human
Run the analyzer on the file you changed:
sf code-analyzer run --rule-selector "PMD:OpinionatedSalesforce" \
--output-file /tmp/analyzer.csv \
--target force-app/main/default/classes/<File>.cls
Scope discipline: the analyzer reports findings on the whole file, but you own only the lines you touched. Findings on lines you did not change are pre-existing — ignore them. Do not investigate or fix them.
Repeat until tests pass and the analyzer reports no new findings on lines you touched.
Commit and push:
git add force-app/main/default/classes/<changed files>
git commit -m "<one short sentence describing the change>"
git push
Every PR description and every reply to a human reviewer must include a clickable auto-login URL to the persistent scratch org so reviewers can manually test the change:
SCRATCH_URL=$(sf org open --url-only --target-org "$SCRATCH_ORG_ALIAS" --json | jq -r .result.url)
(SCRATCH_ORG_ALIAS is set by the workflow to pr-<issue-number>.)
If a PR for the current branch does not yet exist, open one. The body must be specific enough that a reviewer can act without re-reading the issue — but no wall of text. Aim for ~5–10 lines: what changed, how to verify, the scratch org URL, the issue link.
Always write the body to a file and pass --body-file. Don't try to inline it
via --body "$(cat <<EOF...EOF)" — backticks and $() inside the markdown body
collide with shell expansion and the call fails in ways that take several turns
to recover from. Use this exact pattern:
BRANCH=$(git branch --show-current)
if [ -z "$(gh pr list --head "$BRANCH" --json number --jq '.[0].number')" ]; then
cat > /tmp/pr-body.md <<'EOF'
Closes #<issue-number>
## What changed
<2–4 bullets — what classes/methods were modified and why>
## How to verify
- Open the scratch org: <SCRATCH_URL>
- <one or two concrete steps a human can run in the org>
- Apex tests: `sf apex run test --test-level RunLocalTests`
EOF
# Substitute the scratch org URL after the heredoc so the URL itself is not subject to shell expansion.
sed -i "s|<SCRATCH_URL>|$SCRATCH_URL|" /tmp/pr-body.md
gh pr create \
--title "fix: <short description>" \
--body-file /tmp/pr-body.md \
--head "$BRANCH" \
--base main \
--label ai-generated
fi
The single-quoted <<'EOF' heredoc disables shell expansion inside the body, so
backticks for code spans and $variables in narrative both stay literal. The one
substitution we do need ($SCRATCH_URL) is done by sed after the file is on disk.
If the PR already exists, the push above updates it — no new PR. When you reply
to feedback with gh pr comment, include $SCRATCH_URL in the comment body too.
Once the push has succeeded and the summary comment has been posted, stop.
Do not run further gh pr view, git status, git log, or other verification
commands to "double-check" — the work is done, and additional turns burn the budget
without changing the outcome.
create-scratch-org.sh — the workflow already restored or provisioned the org.--source-dir force-app on a feedback run — only deploy the files you changed.git push.