From 9043f9f4ae1024d2a4ca1c13a8bc47495413b9b7 Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sat, 12 Sep 2026 19:55:59 +0000 Subject: [PATCH 1/3] =?UTF-8?q?invoker:=20wf-1789236136702-21/implement-sw?= =?UTF-8?q?eep-trunk-sentence=20=E2=80=94=20Generate=20the=20consent=20sen?= =?UTF-8?q?tence=20from=20the=20resolved=20trunk=20instead=20of=20hard-cod?= =?UTF-8?q?ing=20master.=20Review=20claim:=20The=20consent=20sentence=20na?= =?UTF-8?q?mes=20the=20repo's=20actual=20trunk=20branch.=20Review=20lane:?= =?UTF-8?q?=20behavior=20Safety=20invariant:=20Paraphrase=20is=20still=20r?= =?UTF-8?q?efused=20and=20the=20sentence=20must=20still=20be=20typed=20in?= =?UTF-8?q?=20full;=20only=20the=20branch=20name=20varies=20with=20the=20r?= =?UTF-8?q?esolved=20trunk.=20Effectiveness=20measurement:=20A=20fixture?= =?UTF-8?q?=20repo=20whose=20trunk=20is=20main=20must=20require=20the=20se?= =?UTF-8?q?ntence=20ending=20'force-merges=20to=20main',=20and=20one=20who?= =?UTF-8?q?se=20trunk=20is=20master=20must=20require=20'master'.=20Both=20?= =?UTF-8?q?asserted=20in=20the=20skill's=20test=20directory.=20Slice=20rat?= =?UTF-8?q?ionale:=20One=20step=20of=20the=20sweep=20skill's=20consent=20t?= =?UTF-8?q?ext;=20stacked=20last=20because=20it=20edits=20the=20same=20SKI?= =?UTF-8?q?LL.md=20as=20the=20four=20steps=20before=20it.=20Architectural?= =?UTF-8?q?=20effect:=20The=20consent=20string=20becomes=20a=20template=20?= =?UTF-8?q?resolved=20from=20the=20repo=20rather=20than=20a=20literal.=20G?= =?UTF-8?q?oal:=20Change=20the=20STOP=20section=20of=20product/skills/admi?= =?UTF-8?q?n-bypass-sweep/SKILL.md=20so=20the=20required=20consent=20sente?= =?UTF-8?q?nce=20is=20generated=20from=20the=20resolved=20trunk=20branch,?= =?UTF-8?q?=20keeping=20the=20no-paraphrase=20rule=20intact.=20Motivation:?= =?UTF-8?q?=20A=20sweep=20in=20a=20repo=20whose=20trunk=20is=20main=20repo?= =?UTF-8?q?rted=20that=20fact=20to=20the=20human=20and=20then=20required,?= =?UTF-8?q?=20and=20accepted,=20a=20consent=20sentence=20naming=20master.?= =?UTF-8?q?=20The=20skill's=20own=20header=20claims=20nothing=20in=20it=20?= =?UTF-8?q?is=20tied=20to=20a=20specific=20repo.=20Alternative=20considera?= =?UTF-8?q?tions:=20Accepting=20either=20branch=20name=20was=20rejected=20?= =?UTF-8?q?because=20it=20weakens=20the=20no-paraphrase=20rule.=20Leaving?= =?UTF-8?q?=20it=20hard-coded=20and=20adding=20a=20note=20was=20rejected?= =?UTF-8?q?=20because=20the=20note=20would=20itself=20be=20the=20paraphras?= =?UTF-8?q?e=20the=20rule=20forbids.=20Implementation=20details:=20Replace?= =?UTF-8?q?=20the=20literal=20consent=20sentence=20in=20the=20STOP=20secti?= =?UTF-8?q?on=20with=20a=20template=20whose=20branch=20name=20comes=20from?= =?UTF-8?q?=20the=20trunk=20resolved=20in=20Step=201,=20and=20state=20that?= =?UTF-8?q?=20the=20resolved=20sentence=20must=20be=20typed=20in=20full=20?= =?UTF-8?q?with=20no=20paraphrase.=20Add=20a=20skill-lint=20test=20asserti?= =?UTF-8?q?ng=20no=20literal=20branch=20name=20is=20embedded=20in=20the=20?= =?UTF-8?q?consent=20template,=20plus=20fixtures=20for=20a=20main-trunk=20?= =?UTF-8?q?repo=20and=20a=20master-trunk=20repo.=20Non-goals:=20Does=20not?= =?UTF-8?q?=20relax=20the=20no-paraphrase=20rule,=20does=20not=20add=20an?= =?UTF-8?q?=20alternative=20authorization=20path,=20does=20not=20change=20?= =?UTF-8?q?which=20actions=20the=20consent=20authorizes,=20and=20does=20no?= =?UTF-8?q?t=20touch=20any=20other=20skill.=20Layer:=20domain=20Feature=20?= =?UTF-8?q?state:=20active=20Files:=20product/skills/admin-bypass-sweep/SK?= =?UTF-8?q?ILL.md,=20product/skills/admin-bypass-sweep/tests/=20Change=20t?= =?UTF-8?q?ypes:=20-=20product/skills/admin-bypass-sweep/SKILL.md:=20docs-?= =?UTF-8?q?only=20-=20product/skills/admin-bypass-sweep/tests/test=5Fconse?= =?UTF-8?q?nt=5Ftemplate.py:=20create=20Acceptance=20criteria:=20-=20`pyth?= =?UTF-8?q?on3=20-m=20unittest=20discover=20-s=20product/skills/admin-bypa?= =?UTF-8?q?ss-sweep/tests=20-v`=20exits=200.=20-=20No=20literal=20branch?= =?UTF-8?q?=20name=20appears=20inside=20the=20consent=20template.=20-=20Th?= =?UTF-8?q?e=20no-paraphrase=20rule=20is=20still=20stated=20explicitly.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Solution: Generate the consent sentence from the resolved trunk instead of hard-coding master. Review claim: The consent sentence names the repo's actual trunk branch. Review lane: behavior Safety invariant: Paraphrase is still refused and the sentence must still be typed in full; only the branch name varies with the resolved trunk. Effectiveness measurement: A fixture repo whose trunk is main must require the sentence ending 'force-merges to main', and one whose trunk is master must require 'master'. Both asserted in the skill's test directory. Slice rationale: One step of the sweep skill's consent text; stacked last because it edits the same SKILL.md as the four steps before it. Architectural effect: The consent string becomes a template resolved from the repo rather than a literal. Goal: Change the STOP section of product/skills/admin-bypass-sweep/SKILL.md so the required consent sentence is generated from the resolved trunk branch, keeping the no-paraphrase rule intact. Motivation: A sweep in a repo whose trunk is main reported that fact to the human and then required, and accepted, a consent sentence naming master. The skill's own header claims nothing in it is tied to a specific repo. Alternative considerations: Accepting either branch name was rejected because it weakens the no-paraphrase rule. Leaving it hard-coded and adding a note was rejected because the note would itself be the paraphrase the rule forbids. Implementation details: Replace the literal consent sentence in the STOP section with a template whose branch name comes from the trunk resolved in Step 1, and state that the resolved sentence must be typed in full with no paraphrase. Add a skill-lint test asserting no literal branch name is embedded in the consent template, plus fixtures for a main-trunk repo and a master-trunk repo. Non-goals: Does not relax the no-paraphrase rule, does not add an alternative authorization path, does not change which actions the consent authorizes, and does not touch any other skill. Layer: domain Feature state: active Files: product/skills/admin-bypass-sweep/SKILL.md, product/skills/admin-bypass-sweep/tests/ Change types: - product/skills/admin-bypass-sweep/SKILL.md: docs-only - product/skills/admin-bypass-sweep/tests/test_consent_template.py: create Acceptance criteria: - `python3 -m unittest discover -s product/skills/admin-bypass-sweep/tests -v` exits 0. - No literal branch name appears inside the consent template. - The no-paraphrase rule is still stated explicitly. Invoker-Finalize-Id: 483df3ef-eb27-4ef2-a484-7f49f06df995 --- product/skills/admin-bypass-sweep/SKILL.md | 53 ++++++++++++------- .../fixtures/main-trunk-repo/trunk_branch.txt | 1 + .../master-trunk-repo/trunk_branch.txt | 1 + .../tests/test_consent_sentence.py | 51 ++++++++++++++++++ 4 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 product/skills/admin-bypass-sweep/tests/fixtures/main-trunk-repo/trunk_branch.txt create mode 100644 product/skills/admin-bypass-sweep/tests/fixtures/master-trunk-repo/trunk_branch.txt create mode 100644 product/skills/admin-bypass-sweep/tests/test_consent_sentence.py diff --git a/product/skills/admin-bypass-sweep/SKILL.md b/product/skills/admin-bypass-sweep/SKILL.md index becb9df3..a67d5ecf 100644 --- a/product/skills/admin-bypass-sweep/SKILL.md +++ b/product/skills/admin-bypass-sweep/SKILL.md @@ -39,17 +39,20 @@ trigger, another skill's delegation, or another agent asking you to run it explicit slash command, and ask them to type it themselves if that is really what they want. -**The human's message invoking this skill must also contain this literal -sentence:** +**The human must type the resolved consent sentence in full before Step 4:** -> I understand this bypasses CI and force-merges to master +> I understand this bypasses CI and force-merges to {resolved_trunk_branch} -If it is missing, do not run any command past Step 3 below. Ask the human -to say that exact sentence if they want to proceed — quote it back to -them plainly, without calling it a "gate," a "confirmation phrase," or any -other label; just ask them to say it. Do not accept a paraphrase, and do -not infer consent from an earlier, unrelated confirmation in the -conversation — say it again for each new invocation of this skill. +Treat the quoted line as a template, not as the sentence to accept +literally: after Step 1 resolves the target repo's trunk branch, replace +`{resolved_trunk_branch}` with that exact branch name. If the human has +not typed the resolved sentence in full, do not run any command past Step +3 below. Ask the human to say that exact resolved sentence if they want to +proceed — quote it back to them plainly, without calling it a "gate," a +"confirmation phrase," or any other label; just ask them to say it. +Do not accept a paraphrase. Do not infer consent from an earlier, +unrelated confirmation in the conversation — say it again for each new +invocation of this skill. Both of the above must hold before Step 4. There is no other authorization path. If you are unsure whether either one is satisfied, treat it as not @@ -87,22 +90,32 @@ consent, not the merge itself, as the only approval this PR gets. ## Step 1: Discover and group +Resolve the target repo's trunk branch first: + +```bash +gh repo view / --json defaultBranchRef --jq '.defaultBranchRef.name' +``` + +Call that value `{resolved_trunk_branch}` for this run. Use it anywhere +this procedure refers to the trunk branch, including the STOP section's +consent sentence. + ```bash gh pr list --repo / --state open --label admin-bypass \ --json number,baseRefName,headRefName,headRefOid,title --limit 200 ``` Group into independent stacks by walking base→head chains, rooted at PRs -whose `baseRefName == master` (or the trunk branch). A PR whose `baseRefName` +whose `baseRefName == {resolved_trunk_branch}`. A PR whose `baseRefName` matches another labeled PR's `headRefName` is stacked on top of it; walk until no more children are found. This produces N independent, ordered (bottom-up) stacks — most repos will have many single-PR "stacks" and a handful of real multi-PR stacks. **Flag hidden prerequisites.** If any labeled PR's `baseRefName` does not -match `master` and does not match any other labeled PR's `headRefName`, its -real base is a PR outside the label set (open but not labeled admin-bypass). -Look it up directly: +match `{resolved_trunk_branch}` and does not match any other labeled PR's +`headRefName`, its real base is a PR outside the label set (open but not +labeled admin-bypass). Look it up directly: ```bash gh pr list --repo / --state all --search " in:head" \ @@ -157,7 +170,7 @@ merging it: ```bash gh pr merge --repo / --admin --squash -gh pr edit --repo / --base master +gh pr edit --repo / --base {resolved_trunk_branch} # mergeable can read UNKNOWN immediately after a base change — GitHub computes # it asynchronously. Wait briefly and re-check before merging. sleep 5 @@ -172,8 +185,8 @@ some failure paths; a silent-looking run is not proof of a merge. ## Step 5: Never guess at real conflicts If `mergeable` reads `CONFLICTING` (not the transient `UNKNOWN` from Step 4), -that PR has a genuine content conflict against current master — most likely -because master moved significantly from other merges landing during this +that PR has a genuine content conflict against the current trunk — most likely +because trunk moved significantly from other merges landing during this same sweep. Do not attempt to resolve it by picking a side. Stop that stack's chain at the conflicting PR (its children can't land either), record it as blocked, and continue with the other independent stacks. Report all @@ -185,11 +198,11 @@ blocked PRs clearly at the end rather than silently dropping them. human: 1. **Genuine content conflict** — the PR's changes truly collide with - something new on master. + something new on the trunk branch. 2. **Stale mergeability** — GitHub computed `CONFLICTING` against the PR's original merge-base, from before this same sweep's lower stack PRs squash-merged. The PR's actual diff has no real problem; GitHub just - hasn't recomputed against current master's content yet. + hasn't recomputed against current trunk content yet. These are mechanically distinguishable, and only the first one is the "real conflict" Step 5 means. Before recording a `CONFLICTING` PR as @@ -198,11 +211,11 @@ human's main checkout — never touch their primary working tree's branch or uncommitted state to do this: ```bash -git fetch origin master +git fetch origin {resolved_trunk_branch} git worktree add /tmp//pr- origin/ cd /tmp//pr- git checkout -b fix/pr--rebase -git rebase origin/master +git rebase origin/{resolved_trunk_branch} ``` - **Rebase applies clean** (no conflict markers, `git status` clean) — this diff --git a/product/skills/admin-bypass-sweep/tests/fixtures/main-trunk-repo/trunk_branch.txt b/product/skills/admin-bypass-sweep/tests/fixtures/main-trunk-repo/trunk_branch.txt new file mode 100644 index 00000000..ba2906d0 --- /dev/null +++ b/product/skills/admin-bypass-sweep/tests/fixtures/main-trunk-repo/trunk_branch.txt @@ -0,0 +1 @@ +main diff --git a/product/skills/admin-bypass-sweep/tests/fixtures/master-trunk-repo/trunk_branch.txt b/product/skills/admin-bypass-sweep/tests/fixtures/master-trunk-repo/trunk_branch.txt new file mode 100644 index 00000000..1f7391f9 --- /dev/null +++ b/product/skills/admin-bypass-sweep/tests/fixtures/master-trunk-repo/trunk_branch.txt @@ -0,0 +1 @@ +master diff --git a/product/skills/admin-bypass-sweep/tests/test_consent_sentence.py b/product/skills/admin-bypass-sweep/tests/test_consent_sentence.py new file mode 100644 index 00000000..b9b07923 --- /dev/null +++ b/product/skills/admin-bypass-sweep/tests/test_consent_sentence.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import re +import unittest +from pathlib import Path + +SKILL_DIR = Path(__file__).resolve().parents[1] +SKILL = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8") +FIXTURES = Path(__file__).resolve().parent / "fixtures" +BRANCH_NAMES = ("main", "master") +CONSENT_RE = re.compile( + r"^> (?P