Orchestrate batch dev-to-uat branch promotion across many repos — remote dedup, fast-forward vs back-merge vs uat-only classification, protected branch PR fallback, verification, and per-ticket result comments.
Install
npx skillscat add darellchua2/civiltekk-skills/dev-uat-promotion-skill Install via the SkillsCat registry.
What I do
I am an orchestration skill for promoting dev → uat across a set of
repos (one tracker task per repo — JIRA task or GitHub issue — or ad-hoc). I
do NOT execute git operations
inline. I provide the primary agent with:
- Inventory logic — how to enumerate repos and dedupe remotes (sibling
clones and renamed GitHub repos are the norm, not the exception) - Classification matrix — ff-able / divergent / uat-only / equal, from a
FRESH fetch - Execution invariants — the back-merge-first ordering, no force-push,
main untouched, quarantine-on-failure - Conflict resolution policy — mechanical vs decision-grade conflicts
- Protected-branch fallback — the PR path when direct uat push is
rejected - Ticket hygiene contract — result comment + transition rules (tracker
conventions — JIRA policy perticketing-skill)
Execution is plain git run by the primary agent (or delegated torepo-ops-specialist-subagent with my §Delegation Spec).
When to use me
Invoke me when asked to "promote dev to uat" for one repo or a batch, when
executing promotion tracker tasks (e.g. "Promote dev to uat"), or when/run-worktree-pipeline hits promotion ops tickets (git-refs-only — no feat
branch, PLAN, or PR applies except the protected-branch fallback).
Governance
Content Rule: This skill MUST contain ZERO inline bash scripts and ZERO
inline YAML templates. The process is described as ordered rules; any script
written from it lives in/tmp/opencode/for the run only. This rule is
auditable — inline executable script bodies are a defect.
This skill defers to:
| Aspect | Governing Skill / Agent | Section Reference |
|---|---|---|
| Branch flow & release conventions | version-bump-standard-skill |
§Branch Flow |
| Merge strategy & tag mapping | semantic-release-convention-skill |
§Branch-Aware Tag Mapping |
| New promotion tickets | ticketing-skill |
full flow |
| PR on protected branches | pr-workflow-subagent |
PR creation + gates |
| Bulk execution delegation | repo-ops-specialist-subagent |
§Delegation Spec below |
Inventory & Remote-Identity Dedup
Before ANY classification:
- Enumerate candidate repo directories (e.g.
~/VSCODE/canvastekk-*). - For each, read
git remote get-url originand normalize (strip.git). - Resolve the canonical GitHub name:
gh api repos/<owner/repo> --jq .full_name.
A configured URL may be an OLD NAME of a renamed repo — GitHub redirects
old URLs, so two directories with different configured URLs can be ONE
remote. Confirmed instance:floor-flatness-assessment-demo→canvastekk-point-cloud-app. - Group directories by canonical remote. One promotion per distinct
remote; sibling-directory tickets for the same remote are FULFILLED
(verified + commented), never re-executed.
Classification (per distinct remote, fresh fetch first)
Fetch dev and uat immediately before deciding; never classify on stale
refs (a sibling promotion minutes ago can already have changed the remote).
| State | Test (merge-base) |
Action |
|---|---|---|
equal |
dev SHA == uat SHA | Nothing; report already-equal |
ff-able |
uat is ancestor of dev | Push dev → uat (fast-forward) |
uat-only |
dev is ancestor of uat | Push uat → dev (the back-merge IS the ff), then equal |
divergent |
both have unique commits | Back-merge uat into dev (§Back-Merge Procedure), push dev, then push dev → uat |
Execution Invariants
- Back-merge-first: uat-specific commits land in dev BEFORE any uat push.
dev → uat must always be a fast-forward. - No force-push anywhere. A rejected push is a signal (remote moved, or
branch protected) — re-fetch and re-classify, never retry blindly. - Main untouched. Pushes target
refs/heads/devandrefs/heads/uat
only. - Fresh verification before every push: ancestor checks against
just-fetched refs; pushes use explicit SHAs so a concurrent remote update
causes rejection, not corruption. - Never touch the user's working checkouts beyond
git -C <repo> fetch—
merges happen in throwaway worktrees. - Worktrees:
git worktree add --detach /tmp/opencode/prom-<KEY> <origin/dev SHA>. On failure, QUARANTINE — keep the worktree for
inspection, continue the batch, report. Before re-using a quarantined path,git worktree prune(a stale registration makesworktree addfail with
exit 128). - Back-merge commit message:
chore: back-merge uat into dev before dev-to-uat promotion (<KEY>).
Conflict Resolution Policy
On merge conflict inside the worktree:
- Characterize first:
git diff --name-only --diff-filter=U. - Mechanical (both-append index/list files, e.g.
LEARNINGS/_index.md):
union-merge; then PROVE losslessness — zero lines present in the losing
side but absent from the result (comm -13). Non-zero → treat as
decision-grade. - Modify/delete: decide by lifecycle. A PLAN (or other artifact) for a
Done ticket where dev deleted it as cleanup → deletion wins;
preserve any unique knowledge from the surviving-but-deleted side as a
comment on that artifact's tracker ticket. Anything else → decision-grade. - Decision-grade: quarantine the repo (keep worktree, no pushes), comment
the ticket with the exact conflict, continue the batch, surface to the user.
Protected Branches (push policy classification)
Some uat branches reject direct pushes (e.g. canvastekk-devops: PR-only,
merge commits forbidden, required check check-source-branch). The first
rejected push is the probe:
- On a GH006 protected-branch rejection: back-merge into dev as usual (dev
accepted the merge), then opengh pr create --base uat --head dev. - Watch required checks to green.
- Merge with squash — rebase merge fails when the PR carries a back-merge
commit ("can't be rebased"). - Squash rewrites SHAs, so verify content equality
(git diff origin/dev origin/uatempty) instead of SHA equality, and
record the SHA divergence as protection-design in the ticket comment.
Verification Gate (per remote)
- Direct-push repos:
git ls-remoteshows dev == uat. - Squash-merged protected repos: content-diff empty.
- Re-verify via ls-remote (remote truth), not local refs.
Ticket Hygiene
- Every ticket gets a result comment: action taken, resulting SHAs,
verification method, and that the uat push triggers UAT deploys. - Transition to Done only when the verification gate passed.
blocked-by:unmet → comment the skip reason, leave open.- Fulfilled-by-sibling tickets: comment the dedup evidence (canonical remote
name) and transition Done.
Deployment Warning
Pushing uat triggers UAT deployments (Amplify for FE apps, CI for
services). Surface this before the first push of a batch.
Delegation Spec
Payload for repo-ops-specialist-subagent:
repos: [<abs paths>]
tickets: { <KEY>: { repo, action: ff|back-merge|uat-only, blocked-by? } }
invariants: back-merge-first; no force-push; main untouched;
worktrees in /tmp/opencode/prom-<KEY>; quarantine-on-conflict
verification: ls-remote dev==uat (content-diff for squash-merged)
reporting: per-ticket comment + Done transition on passReturn Contract
complete— every distinct remote promoted and verified; all tickets
commented (Done or justified skip)partial— one or more quarantined or skipped; per-repo table with exact
conflict or blockerfailed— pre-execution abort (missing remotes, git unavailable)