change-backport
Ports a change already merged on one version line of a package to another (`vN/dev` or `support/N.x`). Checks it belongs there, applies it in a worktree, builds, tests and reviews it, and opens a draf
SKILL.mdplugins/umbraco-devkit/skills/change-backport/SKILL.md
GitHubchange-backport
Take one change that already exists on one line and land it on another, as its own reviewed
PR. A port is a small, known change, so this skill makes the edit itself, the way quick-fix
does, and still sends the result through the reviewer.
Scope
- IN: a change with a concrete source (a commit, a merged PR, or a branch) moving to one other line. Ports in either direction.
- OUT: deciding the branch model or which lines exist. That's
git-workflow's "Umbraco packages and version tracking" section. Writing the change in the first place: that'squick-fixor thefeature-*pipeline on the source line. A change that has to be redesigned for the target line (a different extension point, an API that doesn't exist there) is a new change: stop and suggestfeature-exploreon the target line. Moving the whole package to a new CMS major ismajor-upgrade.
Argument
$ARGUMENTS names the source change and, optionally, the target line. If the source is only
a description ("the null check fix from last week"), find the commit or PR and confirm it with
the user. Don't re-derive a fix from a description when a real commit exists. If no target is
given and the project supports exactly two lines, the target is the other one; otherwise ask.
Preflight
Find the lines. Read
CLAUDE.mdfor the lines the project supports, each line's integration branch, and each line's phase (active, security fixes only, or end of life). IfCLAUDE.mddoesn't say, list the remote branches and ask; don't infer a phase from a branch name.Check the target should get it.
- Active: features and fixes.
- Security fixes only: a security fix, nothing else.
- End of life: nothing, unless the user asks for it anyway.
Then check for a reason not to port: the source commit body or PR saying the change only applies to one major (as
git-workflowasks commits to say), or a project memory note. If the change touches something that differs between the lines and you can't tell whether it applies, ask.Check it isn't already there. Search the target line's history for the source PR number, the commit subject, and the changed lines. A partial earlier port means you port only the rest.
Read the whole change. For a PR, list every commit in it, not only the last one. Note any database migration and any public signature change.
Steps
- Cut a fresh worktree on the target line. Create it from the main checkout, never from
inside another worktree. Use the same mechanism
feature-builduses (the project'sWorktreeCreatehook or Claude Code's native worktree), named for the change and the target line, followinggit-workflow's branch naming. Then confirmpwd, the branch, and that its base is the target line's integration branch (git log --oneline -3). If the base is wrong, stop and fix it before applying anything. Porting onto the wrong base is worse than not porting. - Apply the change.
- Clean commits:
git cherry-pick -x <sha>...in their original order.-xrecords the source SHA in each message. - Conflicts: read each one and resolve it by hand. Never take a side wholesale with
--oursor--theirs. - Code that differs on the target line (a renamed type, an API the older CMS major doesn't
have): apply the diff by hand and adapt it to the target line's code. Load the stack
skills the change touches (
umbraco-package-conventions,ef-core-data,umbraco-backoffice-conventions, and so on), the same wayquick-fixdoes. - A database migration: copy the migration files, never generate them again on the
target line. See
ef-core-data's "Porting a migration to another version line". - An
[Obsolete]member: keep the same removal version as the source line, perumbraco-package-conventions.
- Clean commits:
- Verify on the target line. Run the build, the tests, and the lint/format check from
CLAUDE.mdfor the products the change touches. Then smoke it through the real entry point on the target line's demo site, in the orderfeature-build's "Smoke the real entry point" step gives. Passing on the source line proves nothing here. If the site can't run, say "not verified end to end" and why in the hand-off. - Review. Dispatch one
reviewersubagent (Agent tool,subagent_type: reviewer) against the port's diff. Give it the source change as the task, and say what you adapted and why, so it can check the adaptation and not only the code. Fix Critical and Important findings and review again untilPASS, giving each re-review the round's diff and previous findings, as infeature-build's "Recurse" step. If it still fails after three rounds, stop and report the findings. - Commit. Keep the source commit's Conventional Commits type and scope, and its message
where it still describes the change. Add
Port of <source PR or SHA> to <target line>.to the body. - Open a draft PR. Ask before pushing. Then open the PR as a draft against the target
line's integration branch, with
pr-describewriting the body, pergit-workflow. The body links the source PR, and says what was adapted. Draft the matching one-line link for the source PR ("Ported to <target line> in #<n>"). Never merge, however green the checks are.
Hand off
The port PR's link, what was verified (build, tests, end-to-end smoke, or "not verified end to end" and why), the review verdict, what was adapted, and the drafted link for the source PR.
- If another skill started this one, return those and stop. That skill decides what runs next.
- Otherwise, post the source PR link on the user's yes, and offer to remove the worktree once the port PR merges.
Rules
- One target line per run, one worktree per target line. Porting to two lines is two runs.
- Never merge, and never push to an integration branch directly.
- Never skip a line's phase rule without the user asking.
- Never port a change whose source PR is still open. Its review may still change it.