npx skills add ...
npx skills add mimukit/skills --skill prkit
Draft and open a GitHub pull request from your branch, with title, summary, and test plan written from the actual commits and diff, then created with the gh CLI, embedding verifykit proof artifacts inline when a bundle is present. Use when the user asks to open a PR, says "create a pull request", "raise a PR", "submit this for review", or "gh pr create", even if they don't spell out the title or body.
npx skills add mimukit/skills --skill prkit
Turn the commits on the current branch into a clean GitHub pull request: a title in the repo's commit style, a body that explains what changed and why, and a test plan, all inferred from the real diff, not guessed. Creation goes through the gh CLI, reusing the repo's PR template when one exists.
The user wants to open a pull request: "open a PR", "create a pull request", "raise a PR", "submit this for review", "gh pr create". A request for the title and body only ("draft the PR description") takes the Draft-only route, which never pushes, commits, or edits a PR or an issue.
prkit previews each mutation outside its label exemption and waits for an OK: creating or editing the PR, committing a handed-in path, and a force-push after a sync. A caller that carries the user's recorded authorization satisfies the preview for the actions that authorization names. afkkit is the case in point: the user's unattended request authorizes the commits on the issue's own branch, the push of that branch, and the PR, so prkit runs the handed-in QA-plan commit and gh pr create without a prompt, and names the authorization in the hand-off. An action outside the named scope still previews, and an unattended caller escalates it rather than answering for the user: a rebase of a published branch with its --force-with-lease, a push to another branch, a merge. prkit never infers an authorization the caller did not state.
The user wants the title and body, not the PR. This route reads and prints. The index, the refs, the remote, the PR, and the issue labels stay as they were.
git rev-parse HEAD && git diff --cached --binary | git hash-object --stdin && git for-each-ref | git hash-object --stdin. hash-object without -w writes nothing.git fetch, because a fetch moves the remote-tracking refs. Read the remote base tip with git ls-remote origin refs/heads/<base>, compare it with git rev-parse origin/<base>, and say in the draft when the local base ref is behind.gh pr view shows an open PR on the branch, say that the draft would replace that PR's title and body.Done when the title and body are printed, a closing run of the record commands prints the same commit and the same two hashes, and no git push, git commit, git rebase, gh pr create, gh pr edit, or gh issue edit ran. Hand off in one line: say nothing changed, and name the next move, which is to ask prkit to open the PR from this draft.
Confirm the tooling and branch are ready before writing anything:
gh is missing or unauthenticated, say so and point to https://cli.github.com / gh auth login. Don't try to work around it.git branch --show-current is empty, stop: detached HEAD needs a branch before a PR can be opened. Offer to create or switch to one.git switch -c <name>) before continuing, and get the name from gitkit, which owns branch naming, rather than inventing a shape here. Work that traces to an issue gets issue-<n>-<slug>; an urgent fix patched straight onto the base branch gets hotfix-<slug>; anything else keeps whatever name the repo's convention or the human supplies.Get the base branch from gitkit, then read what the branch actually changes; that diff is the raw material for the title and body. Fetch first so every ref below is the real remote state, not a stale local copy:
Read the full diff (git diff origin/<base>...HEAD) only when the commits don't already explain the change. On a branch built through this workflow they usually do, because commitkit wrote each message from the change itself, so the log is a summary of exactly the material a PR body needs, and re-deriving it from the raw diff produces a worse description at many times the cost. Reach for the full diff when the commit messages are thin or generic (a branch of wip and fix typo commits, or work that came from outside this workflow), when the stat shows files no commit message accounts for, or when you need a specific detail for the test plan. Skip lockfiles, build output, and vendored directories either way.
gitkit owns base-ref resolution. Ask it for the base rather than re-deriving the ladder here; repos whose default is develop or trunk are real, and getting this wrong silently produces an empty or enormous diff. Without gitkit, gh repo view --json defaultBranchRef -q .defaultBranchRef.name is the authoritative fallback, and ask rather than guess when it can't answer. Re-check the current branch against the base gitkit returns and stop if they match.
A stack layer's base is the branch below it, not the repo default. When this branch is a layer in a stack, gitkit returns the parent branch, and every origin/<base> below means that branch instead. Two consequences: the diff is the layer's own small change rather than the whole chain, which is the point of stacking, and the "current branch equals base" stop does not fire, because a layer's base is never the branch itself. Ask gitkit whether the branch is a layer rather than inferring it; without gitkit, gh pr view --json baseRefName on an existing PR is the fallback, and a plain branch behaves exactly as it always has.
Diff against origin/<base> (the just-fetched remote tip), not a local <base> that may be behind. Otherwise the title, body, and file list are computed against commits that are no longer the merge target.
Use the commits, branch name (e.g. issue-123-fix-login), and diff to determine the scope, the type of change, and any issue reference (#123, fixes #123). If a linked issue clearly matters and you can't find it, ask rather than invent one.
Before pushing, make sure the branch is up to date with the base tip you just fetched. A PR opened from a stale branch either merges outdated code or lands with GitHub's "This branch has conflicts" banner:
origin/<base> brought in. gitkit owns the sync rule, and it resolves to rebase (git rebase origin/<base>), giving the PR a clean diff. Inside a stack, the rebase is cascading (gh stack rebase --upstack), because replaying this layer moves every layer above it too; hand that to gitkit rather than rebasing the one branch and leaving the layers above pointing at commits that no longer exist. Whether that needs an OK first turns on one thing gitkit states in full: an unpublished branch rebases straight through, because nothing outside this machine points at the commits being rewritten; a branch already pushed previews the rebase and its --force-with-lease together and waits. At PR-open time the branch is usually the former, which is why this step normally runs without a prompt.git diff --name-only --diff-filter=U) and resolve them (or hand them back to the user), then complete the rebase (git rebase --continue). Do not push, and do not open the PR, until the working tree is clean and the sync is finished. If the user declines the sync, say the PR may show conflicts and proceed only if they confirm.Don't re-read the diff after a clean rebase. A rebase replays your commits onto a new base; it doesn't change what they say or do, so the title and body you derived above still describe the branch correctly. The one exception is a rebase you resolved conflicts in, because there you made real edits during the replay, and the resolved result is genuinely different from what you read. Re-read just the files you touched resolving them (git diff origin/<base>...HEAD -- <paths>), not the whole branch.
First, commit any handed-in path. When a caller hands prkit a file that must travel with the branch, most often the manual QA plan that qakit wrote at docs/qa/NNNN-qa-<slug>-YYYY-MM-DD.md and named in its hand-off (directly, or through afkkit), and that file is still uncommitted, commit it here rather than leaving it behind or spawning something else to do it. prkit is already the step that touches git, and it was given the path, so there is nothing to rediscover:
The trailing pathspec commits that path alone. Anything else already staged stays staged and stays out of the commit. A pathspec commit takes the working-tree copy of the path, which is right here, because the handed-in file travels whole. The git show line must print exactly the handed-in path. Any other path in the output means the commit is wrong, so stop and report it before the push.
Only a path the caller named. This is not a licence to sweep the working tree: uncommitted work nobody mentioned is still covered by the rule in Notes, so point it out and offer, don't commit it silently.
The remote branch must exist before a PR can point at it:
If the branch was rebased (Sync with the base branch) and the remote rejects a normal push, use git push --force-with-lease (never bare --force). Don't ask again here: a rejected push means the branch was already published, and gitkit's rule covers the rebase and its lease push under a single confirmation taken during the sync. If that OK wasn't given, because the branch looked unpublished and the rejection is the first sign it wasn't, stop and ask then.
git log, often Conventional Commits like feat(auth): add SSO login). No trailing period.hotfix-<slug> takes the type hotfix, so the title reads hotfix(scope): restore session cookie on refresh. The branch name decides the type, not how bad the bug is, which keeps the two readable against each other in the PR list. Drop back to fix(scope): in a repo whose PR-title lint validates types against the Conventional Commits set, and say once that you did..github/pull_request_template.md (or PULL_REQUEST_TEMPLATE.md) exists, read it and fill it in exactly, matching its sections and checkboxes. Otherwise use: a one-paragraph Summary of what changed and why, a Changes bullet list, and a Test plan (how it was verified, or checkboxes for what to run). Reference the issue in the body (Closes #123) when there is one.git revert <sha>), because a hotfix merges under time pressure and the reviewer needs the undo in front of them. Keep the test plan, and say plainly what you could not verify.Closes #123 is written but inert until the retarget. GitHub honors a closing keyword only on a PR that targets the repository's default branch, so on any other base the keyword is plain text and the issue link never registers. Keep writing Closes #123 anyway, because the keyword takes effect by itself once the layer below merges and GitHub retargets this PR to trunk. Then say so in the stack map, in writing, so the fallback travels with the PR: name the issue this layer closes and state that the link registers after the retarget. A reviewer who sees no linked issue on the sidebar otherwise reads it as a missing reference and adds a duplicate one.This step is optional and runs only when a verifykit proof bundle exists. verifykit leaves a dated bundle at docs/verify/NNNN-verify-<slug>-YYYY-MM-DD/ (slug = the linked issue number, else the feature slug; older bundles have no NNNN- serial) with a ready-to-embed proof.md. If more than one matches, use the highest serial, else the newest creation date; if that still leaves a tie, ask which run to use.
Embed the selected bundle only when it shows this branch head. verifykit opens the bundle's notes.md with fixed lines: commit: <full sha>, dirty: <yes|no>, url:, and captured:. The bundle is fresh when it reads dirty: no and the recorded commit is HEAD, or differs from HEAD in docs only:
The docs exclusion lets the QA-plan commit and the bundle's own commit land after the capture. A bundle with no commit: line, with dirty: yes, with a recorded commit that git diff cannot resolve (a rebase rewrote it), or with any code change since the capture is stale. Embed nothing from a stale bundle, and say in the hand-off which bundle was stale and why; a fresh verifykit run is the fix. Splice the selected fresh proof into the body under a Proof section. The images are already published to a hidden refs/verify-assets/* ref with SHA-pinned raw URLs that render inline, so there's no upload work here; just embed the fragment as-is. If no bundle exists, skip this entirely and open the PR exactly as before. If a bundle exists but its proof.md points at local paths (verifykit couldn't publish, e.g. on a private repo), don't embed dead links: add a short note listing the local artifact paths for manual attachment instead.
First check for an existing PR on this branch so you update instead of duplicating:
state equal to OPEN: update it with gh pr edit --title "…" --body-file <file> rather than opening a second.--body is flaky; --body-file is reliable.Use a path in the system temp dir for the body file and remove it afterward.
Opening several layers of a stack at once takes one command. gh stack submit creates a PR per branch with each base set correctly, which is fiddly and easy to get wrong one gh pr create at a time. This is the half of the stack surface prkit owns; gitkit deliberately does not run it, because gitkit never opens anything.
Write each layer's title and body first, exactly as above, then submit. Opening a single layer stays a plain gh pr create --base <parent>, which is simpler and does not touch the layers above it.
On a layer, check the closing link and report it. Run this once per layer PR, right after it is created or updated, and skip it entirely on a PR whose base is the default branch:
Closes #123 keyword registers then. Nothing is broken and nothing needs repairing.gh issue develop and the createLinkedBranch mutation both refuse a branch that already exists. Waiting for the merge below is the only path.Opening the PR is the moment the linked issue moves from being worked to awaiting review, so flip its lifecycle label in-progress → in-review (the same transition issuekit's sync mode performs when a PR opens). Do this only when the PR references an issue, meaning the #123 / Closes #123 found in Gather context; skip this step entirely if there is none.
gh call yourself:in-progress gets the flip above. An issue carrying ready gets the same shape, --remove-label ready --add-label in-review, and you say in the hand-off that the issue arrived unstarted. ready is a legitimate arrival state because a PR can be opened on work nobody ran issuekit start for, which is the ordinary case when a human just built the thing. It is not the state start leaves behind: start replaces ready with in-progress, so an issue still labeled ready at PR-open time never went through it.ready, in-review is a broken row rather than a richer one. Every downstream reader takes the lifecycle label as a single value, and a second one makes statuskit, sync, and the ready guard disagree about the same issue.blocked, needs-planning, triage, needs-info, already in-review, or no lifecycle label at all, stop and change nothing. Report the state you found. Ask when a human is present; escalate when the run is unattended. A label nobody checked is worse than a label nobody set.in-review label is missing from the repo, point the user at repokit or give gh label create in-review --color 5319E7 --description "a PR is open, awaiting review or merge", and don't mutate around the gap. The exemption skips the prompt, never the provisioning check.Opening a PR is the moment an issue waiting on this issue can become workable, because the code now exists on a branch even though it hasn't merged. Such a dependent can move blocked → stacked and be built on a layer cut from this branch, instead of idling until review finishes. Run this only when the PR closes an issue that something else depends on:
A dependent flips only when every prerequisite permits it. Check each dependent labeled blocked against all its prerequisites, not just this one. A prerequisite other than this issue permits the flip when it is closed, or when its open, non-draft PR sits on a branch this branch already contains (git merge-base --is-ancestor origin/<head> HEAD), because then one layer off this branch has every prerequisite in it. Find a prerequisite's PR by its issue-<p>- head branch. Below gh 2.94.0, read the Blocked by #N body lines instead of blockedBy.
blocked, and report which prerequisite holds it.blocked, and report both PRs. Choose neither one.Run it without asking, under the same label exemption as the in-review flip. These labels relabel a different issue than the one the user named, so state the whole consequence in one line:
PR #51 opened → #52 and #53 move
blocked → stacked, so both can be started now on layers offissue-51-oidc-provider. #54 staysblocked: #49 has no PR yet.
stacked missing from the repo → point at repokit or gh label create stacked --color 006B75 --description "prerequisite is in flight with an open PR; workable now on a branch stacked on it", and don't mutate around the gap.Done when every dependent of this issue either moved to stacked with all its prerequisites checked, or stayed with its holding prerequisite or its competing stack parents named.
Write this section in the procedural register: one instruction per sentence, active voice, present tense, no metaphor.
What changed. Report the PR created or updated (title and number), whether a sync rebase ran, whether a handed-in path was committed, whether a proof section was embedded or a bundle was skipped as stale, whether the linked issue was flipped to in-review, which dependents moved to stacked, and which stayed blocked and why. When a caller's authorization replaced the preview, name the caller and the actions it covered.
Where it landed. Give the PR URL and the branch it points at. Mention that CI will run if configured. On a layer, name the branch it targets and say it is not trunk. On a layer, also report the closing-link check: say the issue link is inert for now, and say it registers after the layer below merges and GitHub retargets this PR.
Next. The PR now waits on review, so the move is on the reviewer's side: mergekit start <n> when it's installed pulls it down into a worktree for local review and QA; otherwise review it on GitHub. A dependent that just moved to stacked outranks that, because it is work the user can start immediately while the review happens: offer issuekit start <n> on the highest-priority one. First offer, don't auto-run, the small follow-ups when they apply: gh pr edit --add-reviewer <user>, --add-label <label>, or gh pr ready for a draft. prkit's job ends here.
gh pr merge is not, unless requested.git status shows staged or unstaged work the user seems to want included, point it out and offer to commit first, rather than silently leaving it behind or committing it without asking.proof.md and embeds it; it never runs the publish itself (that's verifykit's job, with its own bundled script). A stale bundle gets the same treatment as no bundle, plus one line in the hand-off naming it. No verifykit bundle → no Proof section, and prkit works exactly as it always has.gh issue edit. It runs unprompted from in-progress or ready and refuses every other state, because those two are the only ones a PR legitimately arrives from. The exemption is the step's, not the caller's: a human at the keyboard and an unattended orchestrator get exactly the same behavior, and prkit never widens it. No linked issue → prkit opens the PR exactly as before.gh available (e.g. a browser-based agent)? Then you can't push or call gh. Instead read the diff the user provides and print the finished PR title and body as codeblocks for them to paste into the GitHub "New pull request" form.*git rev-list --left-right --count origin/<base>...HEAD # "<behind>\t<ahead>"; left > 0 means behindgit add -- <handed-in path> && \
git commit -m "docs(qa): add manual QA plan for <feature>" -- <handed-in path> && \
git show --name-only --format= HEADgit push -u origin HEADgit diff --quiet <recorded commit> HEAD -- . ':!docs/qa' ':!docs/verify' # exit 0 = no code change since the capturegh pr view --json url,state 2>/dev/nullgh pr create --base <base> --title "…" --body-file <bodyfile>
# add --draft when the user wants a draft, or the work is incompletegh stack submit --auto # a PR per layer, bases already correctgh api graphql -f query='{repository(owner:"<owner>",name:"<repo>"){pullRequest(number:<n>){closingIssuesReferences(first:5){nodes{number}}}}}'gh issue edit <n> --remove-label in-progress --add-label in-reviewgh issue view <n> --json blocking # issues waiting on this one
gh issue view <dep> --json blockedBy,labels # every prerequisite of one dependent
gh pr list --state open --json number,headRefName,isDraft # open PRs, read once
gh issue edit <dep> --remove-label blocked --add-label stacked