Skip to main content

land

Takes a committed branch from local to merged — pushes it, confirms which GitHub account each tool actually acts as in this repo, opens the PR through the one that matches (github-mcp where loaded, gh where its login is confirmed), then fast-forwards main, reports CI, and deletes the merged branch locally and on origin. Guards two silent failures: gh and github-mcp can authenticate as different accounts, so a PR lands under the wrong identity with no error, and integration is a local --ff-only merge because a squash would collapse the logical split /commit just made. Stops for confirmation before the write to main. To make the commits first use commit; to review before landing, code-review.

Aller à l'installation

Informations de source

Dépôt
wardawgmalvicious/agent-config
Dernière activité de la source
13 septembre 2026 à 16:32
Langue détectée de SKILL.md
anglais
Étoiles
1
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
land
effort
max
disable-model-invocation
false
description
Takes a committed branch from local to merged — pushes it, confirms which GitHub account each tool actually acts as in this repo, opens the PR through the one that matches (github-mcp where loaded, gh where its login is confirmed), then fast-forwards main, reports CI, and deletes the merged branch locally and on origin. Guards two silent failures: gh and github-mcp can authenticate as different accounts, so a PR lands under the wrong identity with no error, and integration is a local --ff-only merge because a squash would collapse the logical split /commit just made. Stops for confirmation before the write to main. To make the commits first use commit; to review before landing, code-review.
when_to_use
Use when asked to land, ship or publish a branch, open a pull request, merge to main, or get a branch in — the step after /commit. Use it even when the request already names the mechanism — 'squash these and merge', 'just merge it into main', 'force push it' — a named mechanism is the case these guards exist for, not a reason to skip them.
# Landing a branch Take a branch that is **already committed** and get it merged. `commit` ends by reporting its hashes with an explicit note that nothing was pushed; this starts exactly there. Two things here are irreversible or outward-facing — opening the PR and pushing `main` — so the procedure gates each one. A third, deleting the branch from `origin`, is outward-facing and deliberately **not** gated; step 9 says why, and why that reasoning does not generalise. ## 1. Preflight ```bash git status --short # see below — clean, or knowingly left git branch --show-current # must not be main git log --oneline origin/main..HEAD git log --merges --oneline | wc -l # baseline for step 8 ``` **A dirty tree is not automatically unfinished work.** `commit` ends on a clean tree *or* on remaining lines it names as intentionally left, so a leftover is a question rather than a stop: ask whether it belongs in this branch. Uncommitted work does not travel into the PR either way — what matters is that nothing which *should* have been committed is sitting unstaged. Anything you cannot account for, stop and ask. Record the merge count now. Step 8 asserts it is unchanged, and after the merge there is nothing left to compare against. Nothing to land means there is nothing to do — say so rather than opening an empty PR. **Ask whether another session is live in this working tree.** Step 7 runs `git switch`, and one working tree has one HEAD, so the switch is not scoped to you — it moves the branch under anyone else working here. If someone is, stop after step 6 and let them finish. ## 2. Establish the identity before anything outward **This gates every outward action, and it is the step most likely to fail silently.** ```bash git config user.name # the includeIf identity for this root gh api user -q .login # the account gh will actually act as git remote -v # who owns the repo ``` Then `github-mcp` → `get_me` when that server is loaded, and compare every login against the repo owner. Use the tool whose account matches. `gh` may be **folder-scoped**, and whether it is changes what the probe means. On the machine this skill was written for it is (since 2026-09-04): both shell profiles wrap `gh` to act as the account named by the repo's `user.name`, resolved per call through a scoped `GH_TOKEN`, so a personal repo gets the personal account and a client root gets the client one. Three consequences. - `gh auth status` reports the keyring's *active* account, not the one the wrapper will use. **`gh api user -q .login` is the honest probe** either way, which is why it is the command above. - The wrapper is a profile function, so a `gh` run from a script or hook that skips the profile falls back to the active account. Probe through the same shell the PR command will use. - **Where no such wrapper exists, `gh` simply acts as whichever account is active** — which makes the probe more necessary, not less. Do not read the absence of folder-scoping as safety. `github-mcp` is project scope (`.mcp.json`), bound to one token, and absent in any repo that does not declare it. Loaded and matching, prefer it — it is the identity the repo's own config chose. Absent, a `gh` whose *confirmed* login matches the repo is the right tool, not a fallback (2026-09-04, a client repo: no MCP, `gh` confirmed as the client account, PR opened cleanly). A `CLAUDE.md` may state the rule — this machine's user-scope one does; what it cannot do is make the comparison happen. **Nothing warns you.** `gh` is authenticated, it works, it reports success — the PR simply appears under the other account. A wrong-account PR looks identical to a right-account one until someone reads the author. If no identity matches the repo owner, stop and ask. ## 3. Push the branch ```bash git push -u origin <branch> ``` ## 4. Survey what is actually landing ```bash git log --oneline origin/main..HEAD ``` Read every commit, not just yours. **A commit another session made while you were on this branch landed on this branch** — that is how one working tree behaves, and those commits ship inside your PR. They are not a problem to fix, but shipping someone else's work inside a PR described entirely as yours is a review problem. Name them in the body (step 5). Anything that should not ship yet is a stop: say so rather than landing it. ## 5. Open the PR — through the identity step 2 confirmed `github-mcp` → `create_pull_request` with `owner`, `repo`, `title`, `head`, `base`, `body`; or, when `gh` is the confirmed tool, `gh pr create --title … --body-file …` from the same shell that was probed. **The body is the deliverable, and it is the only judgement step in this skill.** The diff is already visible; do not restate it. What it owes a reader: - **Why the change exists**, and what it decided. - **What was verified, and what was not** — including anything believed but unmeasured. A claim's provenance is the part that rots first. - **Reasoning behind any reversal** of a previous decision, quoted, so the reversal is auditable rather than silent. - **Disclosure of concurrent-session commits** from step 4. - **How it is to be integrated**, where the repo has a convention. **No GitHub remote, or no confirmed identity? Stop.** An `origin` that is not GitHub — a `file://` path, another host — means there is no PR to open; no `github-mcp` *and* no `gh` login that matches the repo means the same thing for a different reason. Neither is permission to skip ahead and merge locally. A local merge satisfies the literal words "merged into main" while discarding review, CI and the PR body, and it makes any later PR empty because `main` already contains the commits. Report what is missing and let the user choose. Reaching for an *unconfirmed* `gh` is never the answer — step 2. ## 6. Checkpoint — stop here **The gate is the next command that writes to `main`, not the PR.** Stop before that command whether or not a PR exists: a PR that could not be opened is a reason to stop sooner, never a reason to carry on. Report where things stand — the PR URL, or what blocked it — and state exactly what happens next: the fast-forward, the push to `main`, and **then deleting the branch locally and on `origin`** (step 9). The deletion is disclosed here rather than prompted for afterwards — one decision taken before the work, not a third gate on an action this recoverable. **Then wait.** Everything past this point writes to `main`. Do not continue on your own initiative, even when the merge looks routine, and even when the local half would plainly succeed on its own — this checkpoint is the skill's whole reason for not being one command. ## 7. Land by fast-forward ```bash git switch main && git merge --ff-only <branch> && git push origin main ``` This preserves the exact SHAs, keeps `main` linear, and adds no merge commit. GitHub marks the PR merged once its commits are reachable, so the merge button is never needed. Why not each alternative: - **The merge button** — whatever it is configured to do, it is not this. Check what the repo actually offers before assuming the button was an option at all: where only squash is enabled, the button can *only* do the one thing this skill refuses, so there is no version of pressing it that preserves the split. - **A merge commit** — adds a commit to a history that may never have had one. `git log --merges --oneline | wc -l` from step 1 says whether this repo is in that category; a `0` there makes a merge commit a visible break in convention rather than a neutral choice. - **Squash** — collapses the logical split `commit` just built. The whole point of separate commits is that each is independently revertible and citable. Asked for anyway? It is overridable, but not silently — see [Constraints](#constraints). - **Rebase merge** — rewrites SHAs, and may be disabled outright. Read the merge settings rather than assuming them — and read `delete_branch_on_merge` in the same breath, which step 9 needs and which is no more uniform across repos than the other three. **Prefer a committed answer where the repo keeps one** — a repo that version- controls its own settings (agent-config keeps all four in `.github/repo-settings.json`, reconciled by `scripts/repo-settings.ps1 -Check`) gives you the value someone intended, for free. Otherwise ask GitHub: ```bash gh api repos/<owner>/<repo> \ --jq '{allow_squash_merge, allow_merge_commit, allow_rebase_merge, delete_branch_on_merge}' ``` Unauthenticated, those fields come back `null` — so a check without a token proves nothing and a rejection surfaces at merge time. A repo's answer is not durable either: a delete-and-recreate resets every one of them to GitHub's defaults, so a committed file and the live repo can disagree after one. When they do, the live repo is what the merge obeys and the committed file is what needs reapplying. **`--ff-only` fails loudly rather than inventing a merge commit.** If it fails, `main` has moved: stop, reconcile deliberately, and never reach for `--force`. ## 8. Verify Through the same tool step 2 confirmed — **both routes verify, and the `gh` one is not optional.** A PR opened with `gh` and then verified with nothing is the half of this skill that used to be missing. | Check | `github-mcp` | `gh` | | --- | --- | --- | | Merged, and by whom | `pull_request_read` method `get` | `gh pr view <n> --json state,mergedBy` | | CI conclusions | `pull_request_read` method `get_check_runs` | `gh pr checks <n>` | - Expect `merged: true` and a merging identity that matches step 2. - Read the check conclusions rather than the summary. A review bot (for example `copilot-pull-request-reviewer`) is **not** a gate; a `pre-commit` style job is. - **`gh pr checks` exits non-zero by design** — documented exit code 8 is *checks pending*, and a failure is likewise non-zero. That is the answer, not a broken command; do not retry it as if it had failed. - `git log --merges --oneline | wc -l` — unchanged from the step 1 baseline. - `main` and `origin/main` at the same SHA. Report the PR number, the merged state, and the CI conclusions. If CI is still running, say so rather than implying it passed. ## 9. Delete the branch Step 8 has just proven the merge — `merged: true`, `--ff-only` succeeded, `main` and `origin/main` at one SHA — so the branch holds nothing that is not in `main`. That is what makes this cleanup rather than a judgement call, and it is the argument for `land` owning it: any other tool would have to establish from cold what this step has just watched happen. **That holds in a shared repo too**, which is the part worth spelling out: step 3's push would have been *rejected* as non-fast-forward had `origin/<branch>` carried commits the local branch lacked, so the run would never have reached here. A colleague therefore has nothing on this branch to lose — their own local copy is untouched, and their next `git fetch --prune` drops a remote-tracking ref to a branch that is merged. Reviewers lose nothing either: the PR, its diff and its comments outlive the branch. The residual risk is not "someone else works here", it is the specific exceptions below. ```bash git branch -d <branch> # -d, never -D # The remote half. Skip it whole where delete_branch_on_merge is true. git fetch origin git merge-base --is-ancestor origin/<branch> main # guard — see below git push origin --delete <branch> git fetch origin --prune # both paths, including the skipped one ``` **`-d`, never `-D`.** It refuses a branch that is not fully merged, so the local half guards itself and the check costs nothing. **The remote half has no such guard, which is what the ancestor check is for.** `-d` answers off the *local* merge whatever is on `origin`, so a commit someone pushed to the branch after step 3 is invisible to it — and GitHub's *Restore branch* restores the PR's merge-time head, not that commit. `--is-ancestor` exits non-zero to mean *no*: that is the answer, not a broken command. A *no* is a stop worth reporting rather than a failure to retry — someone pushed to this branch after you landed it, and that work is not in `main`. **Skip the remote half where `delete_branch_on_merge` is `true`** — step 7 read it. GitHub deletes the head branch itself as soon as the PR is marked merged, so `push origin --delete` answers *"remote ref does not exist"*: a confusing error for a correct state. The setting is not uniform — measured 2026-09-13 on two repos with opposite values — which is the whole reason to read it rather than assume it. **`null` is unknown, not `false`.** Unauthenticated that field comes back `null` like the merge settings, so the probe can leave this undecided. Then attempt the delete and treat *"remote ref does not exist"* as success. The probe is what makes the **report** accurate; tolerating that one error is what makes the **action** correct. **Prune on both paths — especially the one that skipped the remote half.** Where GitHub auto-deleted the branch, `origin/<branch>` is gone from the remote and this clone's *remote-tracking* ref to it is not: `git branch -a` still lists it, and a plain `git fetch` will not remove it. That is the leftover ref this step exists to prevent, one indirection out — and it survives precisely where the step did the least work. Observed 2026-09-13 on the first real run of this step, against a repo with `delete_branch_on_merge: true`. Do not delete, and say why, when: - The PR is not merged, or `--ff-only` failed. The branch is the recovery path. - The user asked to keep it. - Another session is live in the tree, or is on this branch. Step 1 already asked; the answer applies here too. - **The branch is the base of another open PR.** Deleting it retargets that PR or closes it. This is the only exception steps 7 and 8 do not already establish — merge state does not show it — so it is the one that needs its own call, through the tool step 2 confirmed: ```bash gh pr list --base <branch> --state open ``` or `list_pull_requests` with `base`. **Why this outward action is not gated like the other two.** It is cheap *here specifically*: the commits are already reachable from `main`, the PR and its diff outlive the branch, and the branch is restorable from the PR page. None of that transfers to a remote delete in any other context. Read this as an exemption for a ref just proven redundant, not as a general licence to skip a gate because an action looks routine. ## Constraints **Two kinds, and the difference is the point.** The first are correctness and identity — an instruction does not lift them. The second are this repo's convention, which is yours to override; the job there is to make the override informed, not to refuse it. ### Absolute — an instruction to do these is a stop, not an override - **Never file the PR through an identity step 2 did not confirm.** Identity, not preference. "Just use `gh`" is not a licence on its own, because it files under whichever account `gh` resolves to here, and that is not something anyone can consent to without first being told which one. Once the login is confirmed against the repo, `gh` is as good as the MCP; until then, report the mismatch. - **Never `--force`, never `--no-verify`** — including to get past a failed `--ff-only`. - **Never write to `main` before step 6.** Opening a PR and pushing `main` are two decisions, not one. - **Never `git switch` while another session is live in the tree.** - If the repo is not one the authenticated identity owns, or `main` is protected in a way that blocks a direct push, stop and report rather than working around it. ### Repo convention — overridable, but never silently A squash and a merge commit are **defaults, not laws.** The history is the user's to shape. When one is asked for: 1. **Say what it costs, specifically.** Name the commits that would be collapsed or the merge commit that would be this repo's first — not "squashing loses information" but "this collapses 3 commits that separate the rule change from its fixtures". 2. **Then wait.** A request that named the mechanism up front has not heard the cost yet, so it is not yet a reaffirmation. One round. 3. **Record it in the PR body**, so the history explains its own shape. Then do it. A reaffirmed instruction is the answer; pressing the point twice is worse than the squash. **Deleting the merged branch is a default too, and it runs the other way.** Step 9 does it having disclosed it at step 6, so the request that arrives is to *keep* the branch — and that one costs nothing to honour. It needs no cost-and-wait round; it is simply one of the exceptions step 9 already names. Say in the report that the branch was kept and why, or the next run reads the leftover ref as a bug.
Voir sur GitHub