Publish
Owns the whole "I'm done, ship it" pipeline: commit whatever is pending, push, then create or update a draft PR with a description worth reading.
Always: draft PR, default branch as base (from origin/HEAD), auto-push, auto-commit pending changes first.
/schovi:publish # from commit history
/schovi:publish PROJ-123 # from a tracker ticket key
/schovi:publish #123 # from a GitHub issue/PR
/schovi:publish owner/repo#45
/schovi:publish ./spec.md # or ./folder/
/schovi:publish https://any/source/url
/schovi:publish "some text"
Any link is a valid source: a ticket, a doc, a dashboard, a spec page. The plugin's shared references/sources.md (../../references/sources.md from this skill folder) resolves it, whatever the host.
Codex
If custom subagents are unavailable, commit inline and gather context with available Codex tools. For GitHub references use the gh workflow in plugins/schovi/agents/gh-pr-reviewer/AGENT.md.
1. Parse the input
One optional positional argument. Detect its type in this order, most specific first:
- GitHub:
#\d+,owner/repo#\d+, or a GitHub URL - File: the path exists and is a file
- Folder: the path exists and is a directory
- Ticket:
[A-Z][A-Z0-9]{1,9}-\d{1,6} - URL: anything else starting with
http - Text: anything else
- None
With no ticket key in the input, take one from the branch name (PROJ-123-add-auth gives PROJ-123, feature/ABC-456-fix-bug gives ABC-456). Store it as TICKET_KEY.
2. Check the git state
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
git rev-parse --abbrev-ref HEAD
git status --porcelain
gh auth status
gh pr list --head "$(git branch --show-current)" --json number,url,title,isDraft,state
Stop if you're on the default branch (they need a feature branch) or gh isn't authenticated. Commit any pending changes before continuing, and say that you're doing it. An existing PR for this head means UPDATE mode; nothing found means CREATE.
3. Push
The local branch name must always equal the remote branch name, and therefore the PR's headRefName. An upstream pointing somewhere else (origin/main left over from a merge queue) has to be cleared first, or the push creates a mismatched remote branch.
LOCAL_BRANCH=$(git branch --show-current)
UPSTREAM=$(git rev-parse --abbrev-ref @{u} 2>/dev/null)
if [ -n "$UPSTREAM" ] && [ "${UPSTREAM#origin/}" != "$LOCAL_BRANCH" ]; then
git branch --unset-upstream
fi
git push -u origin "$LOCAL_BRANCH"
git ls-remote --heads origin "$LOCAL_BRANCH"
Never push with a local:different-remote refspec.
4. Gather context
- Any source type except
None: read../../references/sources.mdand follow it. It picks the fetcher (ticket tracker, GitHub, observability vendor, doc tool, plain web page, local file) and hands back a canonical URL, a title, the what and why, and any acceptance criteria - None: read the commits
A TICKET_KEY off the branch is also a source. Resolve it the same way when the input itself gave nothing, and keep publishing if it can't be resolved.
git log origin/$DEFAULT_BRANCH..HEAD --format="%s%n%b" --reverse
git diff origin/$DEFAULT_BRANCH..HEAD --stat
The Context section needs at least one real link. Collect candidates from the input source, the TICKET_KEY off the branch, and any links in the commits or fetched content (dashboards, related PRs, specs, design docs). If you come up empty, ask for one before writing the description:
No source link found for this change. Paste a relevant link (ticket, dashboard,
related PR, spec, design doc) so reviewers have context, or reply
"skip" to publish without one.
Never invent a link. On "skip", omit the section.
5. Write the description
The reviewer reads the code. So the description does not explain how the code works or which files changed. It carries what a reader cannot recover from the diff: what was decided, and why.
Describe the final state, never the evolution. "The API returns paginated results", not "changed the API to return paginated results". This matters most in UPDATE mode: rewrite the description from scratch for the code as it now stands. No "we changed X to Y", no "updated A to B".
Describe presence, never absence. Drop "no schema change", "no behavior change", "nothing else touched" unless you have read the diff and confirmed it. Commit messages and file stats do not prove a negative, and an unverified absence claim waves the reviewer off exactly where they should look.
No agent-process content. The description is about the change, not about how you produced it. Leave out TODO checklists, validation narration ("tests pass", "verified locally"), workflow narration ("first I explored the codebase"), and self-reference ("I decided", "as requested"). If validation matters to a reviewer, it belongs in Review Notes as a fact.
Classify it as Bug, New Feature, Enhancement, or Chore, then:
## [Bug | New Feature | Enhancement | Chore]
[1-2 sentences on the problem or feature. Link to the spec/ticket/doc rather than restating it.]
## Context
[Links that give a reviewer the full picture: the ticket, a dashboard, related PRs, the product spec, a design doc, a Slack thread. One per line with a short label.]
## Decisions (only if applicable)
- [Decision] — [why: solves X / improves Y / forced by Z]
## Review Notes (only if applicable)
[Only what the reviewer must do or watch for that isn't in the diff and isn't said above.]
## Notes (only if applicable)
### Breaking Changes
### Migration
[What consumers of the merged code need when upgrading.]
Be short. Cut anything the diff already shows.
Decisions is the heart of it: the meaningful choices and the reason for each, one line as decision — why. The "why" has to add what the code can't (it solves a problem, it was forced by a constraint, it beat a specific alternative). No implementation detail. No decisions, no section.
Review Notes is addressed to the reviewer about reviewing and merging. Test every bullet: could they get this from the diff or the sections above? Then cut it. What survives is required actions, merge sequencing, follow-ups, the single riskiest spot, and looks-wrong-but-intentional gotchas. Upgrade steps for consumers go under Notes > Migration instead.
Title: PROJ-123: Description with a ticket key, otherwise just the description. 50-80 characters, active voice (Add, Fix, Implement, Update), no trailing period. Source it from the spec title, the source's own title, or the theme of the commits.
6. Ship it
# CREATE
gh pr create --draft --title "[TITLE]" --base "$DEFAULT_BRANCH" --body "$(cat <<'EOF'
[DESCRIPTION]
EOF
)"
# UPDATE
gh pr edit [PR_NUMBER] --body "$(cat <<'EOF'
[DESCRIPTION]
EOF
)"
Report the URL, the branch pair, and for a new PR the follow-ups worth knowing: gh pr edit N --add-reviewer @user, gh pr ready N, gh pr checks N --watch.