Skip to content

feat: Move-Commit -UnderPRInStack (stack a commit under an existing PR) - #24

Draft
dkattan wants to merge 1 commit into
mainfrom
feature/under-pr-stack
Draft

feat: Move-Commit -UnderPRInStack (stack a commit under an existing PR)#24
dkattan wants to merge 1 commit into
mainfrom
feature/under-pr-stack

Conversation

@dkattan

@dkattan dkattan commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a -UnderPRInStack parameter set to Move-Commit that automates the real-world workflow: "I have a commit in this PR I don't want bloating it — move that code to another branch/worktree/PR stacked UNDER this one, control draft status, then spawn another agent in that worktree."

Non-destructive by default: the source branch is never rewritten or force-pushed. The moved commit leaves the source PR's diff purely because the diff becomes lower..source (which excludes it). -RemoveFromSource remains the opt-in rewrite path.

What it does

Cherry-picks the commit onto a new lower branch (in a persistent, user-owned -WorktreePath handed to a second agent), pushes it, opens a draft PR, repoints the source PR's base to the lower branch, and registers the GitHub-native stack.

Move-Commit -UnderPRInStack -WorktreePath <path-to-persistent-worktree>
# zero extra args: title derives from the commit message, source PR derives from the branch

Architecture: a new recorder plan core

The existing New-MoveCommitPlan builds ~250 lines of PowerShell as string literals — hard to extend to a flow that's 60% live gh side-effects. This PR adds a recorder plan core where ops are data nodes with two interpreters (execute-live + serialize-to-script), so strings become an output of serialization, never an input. This is the tagless-final/command pattern (precedented in theory via Darcs, novel in git tooling).

  • Additive onlyMove-Commit's existing behavior is untouched (DefaultParameterSetName='Move'). The recorder is used by the new parameter set only; retrofitting Move-Commit is a deferred follow-up.
  • Narrow DSL by design: per-op OnFailure, one finally, When guards, Down rollback companions, $runtime: token capture. No general control-flow AST.

Stack registration: gh stack link, not gh stack submit

Source-level verification of gh-stack v0.0.8 showed gh stack submit --auto fails in this flow (requires local .git/gh-stack tracking state we never create) and would re-touch the base from a divergent local model. gh stack link <lower-pr> <source-pr> needs no local state, computes bases from argument order, reuses existing PRs, and is a no-op confirmation when the base is already correct.

Idempotent re-run + rollback (D8)

Because the default is COPY (no source rewrite), the whole flow is idempotently re-runnable — primary recovery is "re-run the command." Pre-flight fail-fast checks and per-step "already done?" resume checks are one mechanism used twice. Each op carries a Down (undo) companion; on failure it reports the exact rollback commands by default, and -RollbackOnFailure auto-plays them. The cherry-pick step uses git patch-id --stable to skip when the patch is already applied (sanity-checked against real git).

Test plan

  • 285 Pester tests pass (243 original + 42 new), no regressions, zero PSScriptAnalyzer errors.
  • New tests cover: recorder op model, builders-that-don't-execute, both interpreters, $runtime: capture; gh/stacks runners, branch→PR resolution, stale-stack discovery, provider dispatch; the under-stack flow's parameter set, zero-arg ergonomics, the persistent-worktree guardrail (no worktree remove), patch-id idempotency, 3-way worktree detection, D5 stale-unstack ordering, D7 draft/title, and D8 failure reporting + rollback buffer.
  • Not yet run: live end-to-end against a real repo with stacks (immybot). Unit/snapshot/mock coverage is comprehensive; the live e2e is a follow-up before merge.

Design provenance

The 9 design decisions (D1–D9) were settled via the Wayfinder approach — charted as tickets, resolved one at a time, with the two load-bearing primitives (git patch-id --stable, git worktree list --porcelain) sanity-checked against real git before encoding. Key decisions recorded in repo memory.

Follow-ups (deferred by design)

  • Retrofit Move-Commit/New-MoveCommitPlan onto the recorder.
  • Persisted phase-tracked plan with --continue/--abort (only if transient failures prove frequent in practice).
  • Live e2e against immybot before merge.

🤖 Generated with Claude Code

…existing PR

New parameter set on Move-Commit that cherry-picks a commit onto a new lower
branch (in a persistent, user-owned worktree), pushes it, opens a draft PR,
repoints the source PR's base to the lower branch, and registers the
GitHub-native stack. Non-destructive by default: the source branch is never
rewritten or force-pushed (the moved commit leaves the source PR's diff because
the diff becomes lower..source). -RemoveFromSource remains the opt-in rewrite path.

Built on a new recorder plan core (op nodes as data with two interpreters:
live execution + serialize-to-script), so strings are an output of serialization
rather than hand-authored input — the "code in strings" smell from
New-MoveCommitPlan. Additive only; Move-Commit's existing behavior is untouched
(DefaultParameterSetName='Move').

- Recorder: New-PipelineOp/New-Pipeline + Invoke-GitOp/Invoke-GhOp/Invoke-
  StackOp/Invoke-GhApiOp builders (append, never execute), Invoke-Pipeline
  (live) and ConvertTo-PipelineScript (serialize), with per-op OnFailure/When/
  Down and $runtime: token capture for runtime-resolved PR numbers.
- gh/stacks machinery: Invoke-GhCommand/Invoke-StackCommand/Invoke-GhApi runners
  (mirror Invoke-GitQuery), Resolve-GitSplitPullRequestFromBranch,
  Find-GitSplitStaleStacks (hawkins-preview), New-GitStackProvider
  (gh-stack/graphite/none/auto). gh-stack registers via `gh stack link <pr> <pr>`
  (source-verified: submit requires local tracking state this flow never creates).
- Flow: New-MoveCommitUnderPRPlan — discovery, pre-flight fail-fast, 3-way
  worktree detect-and-branch (persistent, never torn down), patch-id --stable
  cherry-pick idempotency, reactive D5 stale-stack unstack, base repoint,
  provider stack registration, Up/Down rollback buffer (D8 report-by-default,
  -RollbackOnFailure auto-plays).

Tests: 285 Pester tests pass (243 original + 42 new), no regressions, zero
PSScriptAnalyzer errors. D1-D9 design decisions settled via the Wayfinder
approach and recorded in memory; patch-id and worktree-list primitives
sanity-checked against real git before encoding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant