Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions skills/git-remote-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ Open or maintain one GitHub pull request for the entire committed current branch
- The explicit PR request authorizes read-only preparation. In normal mode, show the preview below and **stop for the exact `approve APR-...` phrase displayed in that preview before any remote write**. Approval for Preview A can execute only Plan A.
- `yolo` or `auto` attached to the same explicit PR request still requires the full preview, but it skips the approval requirement itself. Show the same preview as status, do not ask for or wait on `approve APR-...`, and continue immediately with that just-presented plan's ID. Neither mode authorizes force push, rebase, reset, amend, merge, branch deletion, auto-merge, or unrelated writes.
- A dirty worktree, including untracked files, blocks both modes. Explain that a PR contains committed changes only. Do not stage, commit, stash, discard, or automatically invoke `git-visual-commits`.
- A missing upstream tracking branch, or a local/upstream branch-name mismatch, also blocks both modes. The skill never guesses a PR head after a rename or silent tracking drift; fix tracking first, then rerun the workflow.
- A branch with no tracking configuration is a normal first-publication case: use the GitHub `origin` remote, or the sole GitHub remote when no GitHub `origin` exists, and the exact current branch name. Prepare the full preview before publishing; execution uses a normal `git push --set-upstream` to publish and establish tracking. `create yolo` performs that push and creates the PR without another approval reply. Ambiguous remotes, incomplete tracking configuration, and local/upstream branch-name mismatches block both modes; never silently retarget existing tracking after a rename.
- Commit requests belong to `git-visual-commits`; squash wording belongs to `git-visual-squash-summary`; release notes belong to `git-remote-release`; changelogs belong to `git-keep-a-changelog`. This skill independently reads the complete PR comparison.

## Phase 1: prepare

1. Check `git` and `gh` are available. Run the read-only collector from the current working directory, with an absolute skill path. Use a unique operating-system temp directory, or ignored `.bot/` when needed. The collector fails closed for detached `HEAD`, the base branch, dirty worktree, missing or mismatched upstream tracking, missing auth/assignee eligibility, empty comparison, diverged remote head, and ambiguous templates. It discovers the GitHub repository, authenticated account, actual default branch, fork parent when applicable, upstream head branch, open PR, and push need. If the local branch was renamed or never tracked, repair the upstream first with `git push --set-upstream origin HEAD` or `git branch --set-upstream-to=origin/<branch>` after the remote branch name is correct. Supply `-Repository owner/repo` or `-Base branch` only when explicitly chosen or needed to resolve real ambiguity.
1. Check `git` and `gh` are available. Run the read-only collector from the current working directory, with an absolute skill path. Use a unique operating-system temp directory, or ignored `.bot/` when needed. The collector fails closed for detached `HEAD`, the base branch, dirty worktree, ambiguous publication remotes, incomplete or mismatched tracking, missing auth/assignee eligibility, empty comparison, diverged remote head, and ambiguous templates. It discovers the GitHub repository, authenticated account, actual default branch, fork parent when applicable, effective head branch, open PR, push need, and whether tracking must be established. Missing tracking does not require a preliminary push or configuration edit: compare local `HEAD` against the fetched base, then preview publishing the exact current branch to the resolved remote. Valid same-name tracking may point to a branch not yet published or fetched. If existing tracking names a different branch, stop and explain the mismatch. Supply `-Repository owner/repo` or `-Base branch` only when explicitly chosen or needed to resolve real ambiguity.

```text
pwsh -NoProfile -NonInteractive -File <skill>/scripts/prepare-pr.ps1 -OutputDirectory <temp-directory>
Expand Down Expand Up @@ -70,7 +70,7 @@ Before any `git push`, `gh pr create`, `gh pr edit`, assignee change, or equival
- Proposed **title** and the **complete exact body** that will be submitted, preserving Markdown headings, bullets, and checklists. Never substitute a synopsis, excerpt, or promise to show the body later.
- Commit count, changed-file count, authenticated assignee, and whether a normal branch push is required.
- Existing PR URL on update, plus whether its **entire current body** will be replaced and whether title/body/assignment need changes.
- Exact planned writes: optional normal push to the already-tracked head branch, create or edit the intended PR, and add the authenticated assignee if absent. Include ready/draft state.
- Exact planned writes: optional normal push to the resolved head branch, establishing upstream tracking when absent, create or edit the intended PR, and add the authenticated assignee if absent. Include ready/draft state.
- A prominent `Approval ID: APR-...` and the final instruction `To approve this exact preview, reply: approve APR-...`, using the same ID as `plan.json.approval_id`.

In normal mode give the preview link and its exact approval phrase, then stop. Bare `approved`, `yes`, `go ahead`, or `looks good` does not authorize execution. Re-present the **existing preview link** and ask for the exact displayed phrase; do not regenerate anything or infer, fill in, or manufacture the user's approval ID. With same-request `yolo`/`auto`, give the same preview link and phrase as status only, do not ask for or wait on that phrase, and pass that just-presented plan's ID immediately. No approval is inferred from a prior unrelated `yolo`/`auto`.
Expand All @@ -89,7 +89,7 @@ The helper matches the supplied approval ID, checks the exact final preview hash

Only an explicit subsequent `refresh` / `retry with new state` request starts another transaction: use a **new scratch workspace**, collect fresh evidence, write a fresh body, and create a fresh plan and preview with a new ID. Keep the previous artifacts unchanged. Present the new preview link and exact new approval phrase; normal mode waits for that new phrase. Approval ID A cannot execute Plan B. The same-request `yolo`/`auto` exception applies only if attached to this new refresh request.

Never force push. A non-fast-forward/diverged branch fails closed. The helper pushes normally only if required, verifies remote SHA, rechecks the comparison after push, writes the PR and assignment, then fetches GitHub metadata and every PR file page. It verifies base, head, title, full body, draft/ready state, assignee, head SHA, and changed-file statuses, including rename sources. Do not declare success if any check fails. If a push or PR creation succeeded before a later failure, report the partial state and URL when available and stop; do not regenerate or retry automatically.
Never force push. A non-fast-forward/diverged branch fails closed. The helper pushes normally only if required, includes `--set-upstream` when the preview plans establishing tracking, verifies remote SHA, rechecks the comparison after push, writes the PR and assignment, then fetches GitHub metadata and every PR file page. It verifies base, head, title, full body, draft/ready state, assignee, head SHA, and changed-file statuses, including rename sources. Do not declare success if any check fails. If a push or PR creation succeeded before a later failure, report the partial state and URL when available and stop; do not regenerate or retry automatically.

The legacy `-Approved` switch is insufficient. Pass an ID only from the user's exact approval phrase or the scoped same-request `yolo`/`auto` exception. Keep approval artifacts outside the working tree or under ignored `.bot/` and retain them at handoff and on failure. No live PR creation or model-backed eval execution is part of skill validation.

Expand Down
16 changes: 14 additions & 2 deletions skills/git-remote-pr/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
},
{
"id": 6,
"prompt": "Open a pull request from this checkout. Test detached HEAD, then a feature branch with no upstream configured, then the default branch, then a feature branch with an empty base...head comparison.",
"prompt": "Open a pull request from this checkout. Test detached HEAD, then a feature branch with incomplete upstream configuration, then the default branch, then a feature branch with an empty base...head comparison.",
"expected_output": "Each invalid state fails before any remote write with a distinct actionable diagnosis.",
"expectations": ["Detached HEAD blocks", "Missing upstream tracking blocks", "Current default/base branch blocks", "Empty comparison blocks", "No PR is created"]
"expectations": ["Detached HEAD blocks", "Incomplete upstream configuration blocks", "Current default/base branch blocks", "Empty comparison blocks", "No PR is created"]
},
{
"id": 7,
Expand Down Expand Up @@ -120,6 +120,18 @@
"prompt": "Refresh the PR preview; retry with new state. The previous transaction /tmp/pr-review-a was invalidated when the branch changed from 5 commits / 9 files to 37 commits / 66 files. I had approved APR-4F8C2A91D6B3 for A. This request authorizes fresh preparation only.",
"expected_output": "Starts a new scratch workspace, collects fresh evidence, writes a fresh body, prepares Preview B and Plan B with a different approval ID, and waits for approval of B's exact displayed phrase.",
"expectations": ["The explicit refresh starts a new transaction rather than resuming A", "All original A artifacts remain unchanged", "Fresh evidence, body, plan, and preview are produced in a new workspace", "Approval B differs from A and is shown in B's preview and exact approval phrase", "The response links Preview B and asks for its exact phrase", "Approval A, generic approval, or absence of approval B cannot execute Plan B", "Prior yolo or auto permission is not carried into this refresh"]
},
{
"id": 21,
"prompt": "git-remote-pr create yolo in a clean checkout of codebeltnet/cuemon on v10.8.0/options-enhancement. Origin is https://github.com/codebeltnet/cuemon.git. This committed feature branch has no upstream configuration and has never been pushed. Create the PR and push the branch to origin.",
"expected_output": "Prepares the complete local comparison and a preview planning normal publication to origin/v10.8.0/options-enhancement with upstream tracking, PR creation, and authenticated assignment. Presents the preview link and immediately executes the same plan without asking for approval, then verifies the published branch and PR.",
"expectations": ["Missing upstream tracking is accepted as first publication, with the exact current branch name on origin", "Preparation performs no preliminary push or branch tracking edits", "The preview explicitly includes normal push and upstream tracking", "Same-request yolo continues without waiting for an approval reply", "Execution publishes the approved HEAD and establishes upstream tracking before PR creation", "The verified PR URL and result are returned without claiming success on partial failure"]
},
{
"id": 22,
"prompt": "Create a PR from a clean committed feature branch with no tracking configuration. There is no origin remote and two distinct GitHub remotes. Do not guess which repository I intend to publish to.",
"expected_output": "Reports ambiguous publication remotes and asks for an explicit tracking target, without pushing or altering configuration. A sole GitHub remote would be usable without clarification.",
"expectations": ["Multiple GitHub remotes without origin are not silently selected", "No push, tracking edit, or PR mutation occurs", "The diagnosis identifies the missing publication target rather than requiring an unnecessary preliminary push"]
}
]
}
8 changes: 7 additions & 1 deletion skills/git-remote-pr/scripts/execute-pr.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ try {
if ($Draft -and $expected.existing_pr -and -not $expected.existing_pr.draft) { throw 'An existing ready PR cannot be converted to draft by this workflow.' }
if ($fresh.push_required) {
$writesStarted = $true
Invoke-Git @('push', $fresh.head_remote, "HEAD:refs/heads/$($fresh.remote_branch)") | Out-Null
$pushArgs = @('push')
if ($fresh.set_upstream_required) { $pushArgs += '--set-upstream' }
$pushArgs += @($fresh.head_remote, "HEAD:refs/heads/$($fresh.remote_branch)")
Invoke-Git $pushArgs | Out-Null
$pushDone = $true
}
$remoteSha = Get-RemoteSha $fresh.head_remote $fresh.remote_branch
Expand All @@ -94,6 +97,9 @@ try {
try {
$afterResult = Invoke-Tool pwsh @('-NoProfile', '-NonInteractive', '-File', (Join-Path $PSScriptRoot 'prepare-pr.ps1'), '-OutputDirectory', $afterPath, '-Repository', $expected.repository, '-Base', $expected.base)
$after = Get-Content -LiteralPath ((ConvertFrom-Json -InputObject $afterResult.Text).evidence) -Raw -Encoding utf8 | ConvertFrom-Json
if ($after.set_upstream_required -or $after.head_remote -cne $fresh.head_remote -or $after.remote_branch -cne $fresh.remote_branch) {
throw 'Upstream tracking verification failed after push. Stop before writing PR metadata.'
}
if ($after.head_sha -cne $fresh.head_sha -or $after.base_sha -cne $fresh.base_sha -or $after.merge_base -cne $fresh.merge_base -or $after.changed_file_count -ne $fresh.changed_file_count) {
throw 'The effective comparison changed after push. Stop before writing PR metadata.'
}
Expand Down
7 changes: 6 additions & 1 deletion skills/git-remote-pr/scripts/make-plan.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ try {
$metadata = if (-not $evidence.existing_pr) { 'CREATE' } elseif ($evidence.existing_pr.title -cne $Title -or $evidence.existing_pr.body -cne $body) { 'UPDATE' } else { 'NONE' }
$plannedDraft = if ($evidence.existing_pr) { [bool]$evidence.existing_pr.draft } else { [bool]$Draft }
$writes = [System.Collections.Generic.List[string]]::new()
if ($pushRequired) { $writes.Add("Normal push to $($evidence.head_remote)/$head") }
if ($pushRequired) {
$pushWrite = "Normal push to $($evidence.head_remote)/$head"
if ($evidence.set_upstream_required) { $pushWrite += '; set upstream tracking to the same branch' }
$writes.Add($pushWrite)
}
if ($metadata -eq 'CREATE') { $writes.Add('Create PR with the title and complete body below') }
if ($metadata -eq 'UPDATE') { $writes.Add('Edit PR with the title and complete body below') }
if ($assignmentWrite) { $writes.Add("Assign $assignee") }
Expand All @@ -64,6 +68,7 @@ try {
head_repository = $headRepository
assignee = $assignee
push_required = $pushRequired
set_upstream_required = [bool]$evidence.set_upstream_required
metadata_write = $metadata
assignment_write = $assignmentWrite
existing_pr_number = if ($evidence.existing_pr) { $evidence.existing_pr.number } else { $null }
Expand Down
5 changes: 3 additions & 2 deletions skills/git-remote-pr/scripts/pr-common.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ function Get-PlanApprovalId { param($Plan)
# with identical content. review_hash binds the complete normalized preview;
# preview_hash checks the final artifact independently to avoid a cycle.
$fields = [ordered]@{}
foreach ($name in @('schema', 'snapshot_key', 'evidence_file', 'evidence_hash', 'body_file', 'body_hash', 'preview_file', 'repository', 'action', 'base', 'head_repository', 'head', 'title', 'draft', 'assignee', 'push_required', 'metadata_write', 'assignment_write', 'existing_pr_number', 'existing_pr_url', 'commit_count', 'changed_file_count')) {
foreach ($name in @('schema', 'snapshot_key', 'evidence_file', 'evidence_hash', 'body_file', 'body_hash', 'preview_file', 'repository', 'action', 'base', 'head_repository', 'head', 'title', 'draft', 'assignee', 'push_required', 'set_upstream_required', 'metadata_write', 'assignment_write', 'existing_pr_number', 'existing_pr_url', 'commit_count', 'changed_file_count')) {
$fields[$name] = $Plan.$name
}
$fields['review_hash'] = $Plan.review_hash
Expand Down Expand Up @@ -146,7 +146,7 @@ function Get-PrDriftDiagnostic { param($Approved, $Current)
$lines.Add("- Commits: $($value.commit_count)")
$lines.Add("- Changed files: $($value.changed_file_count)")
}
foreach ($name in @('repository', 'head_repository', 'head', 'head_remote', 'remote_branch', 'remote_sha', 'merge_base', 'assignee', 'template_path')) {
foreach ($name in @('repository', 'head_repository', 'head', 'head_remote', 'remote_branch', 'remote_sha', 'set_upstream_required', 'merge_base', 'assignee', 'template_path')) {
$before = ConvertTo-Json -InputObject $Approved.$name -Depth 8 -Compress
$after = ConvertTo-Json -InputObject $Current.$name -Depth 8 -Compress
if ($before -cne $after) { $lines.Add("- ${name}: $before -> $after") }
Expand Down Expand Up @@ -195,6 +195,7 @@ function Get-PrSnapshotKey { param($Evidence)
repo = $Evidence.repository; head_repo = $Evidence.head_repository
base = $Evidence.base; head = $Evidence.head
head_remote = $Evidence.head_remote; remote_branch = $Evidence.remote_branch
set_upstream_required = $Evidence.set_upstream_required
assignee = $Evidence.assignee; template_path = $Evidence.template_path
base_sha = $Evidence.base_sha; head_sha = $Evidence.head_sha
remote_sha = $Evidence.remote_sha; merge_base = $Evidence.merge_base
Expand Down
Loading
Loading