diff --git a/product/skills/admin-bypass-sweep/SKILL.md b/product/skills/admin-bypass-sweep/SKILL.md index becb9df3..44aa6969 100644 --- a/product/skills/admin-bypass-sweep/SKILL.md +++ b/product/skills/admin-bypass-sweep/SKILL.md @@ -193,32 +193,27 @@ human: These are mechanically distinguishable, and only the first one is the "real conflict" Step 5 means. Before recording a `CONFLICTING` PR as -blocked, check which case it is, in a disposable worktree outside the -human's main checkout — never touch their primary working tree's branch or -uncommitted state to do this: +blocked, run the rebase probe outside the human's main checkout — never +touch their primary working tree's branch or uncommitted state to do this: ```bash git fetch origin master -git worktree add /tmp//pr- origin/ -cd /tmp//pr- -git checkout -b fix/pr--rebase -git rebase origin/master +scripts/probe_branch_rebase.sh origin/ origin/master ``` -- **Rebase applies clean** (no conflict markers, `git status` clean) — this - was stale mergeability, not a real conflict. Force-push the rebased - branch back to the PR's head with `--force-with-lease` pinned to the - known old SHA, wait for GitHub to recompute (`sleep 5`), confirm - `mergeable` now reads `MERGEABLE`, then continue this PR (and its - children) through Step 4 as normal. -- **Rebase stops with conflict markers** — this is Step 5's real-conflict - case. Run `git rebase --abort`, remove the scratch worktree, and follow - Step 5 as written: stop the chain, record as blocked, move on. Do not - attempt to resolve the markers by picking a side — that part of Step 5 - still applies. - -Remove the scratch worktree (`git worktree remove --force`) once the PR's -fate — merged or genuinely blocked — is decided. +- **Exit 0, `OK`** — the PR rebases cleanly onto current master, so this was + stale mergeability rather than Step 5's real-conflict case. Wait for + GitHub to recompute (`sleep 5`), confirm `mergeable` now reads + `MERGEABLE`, then continue this PR (and its children) through Step 4 as + normal. +- **Exit 1, `FAIL`** — the probe ran and found a real content conflict. + Follow Step 5 as written: stop the chain, record as blocked, and move on. + Do not attempt to resolve the markers by picking a side — that part of + Step 5 still applies. +- **Exit 3, `UNCHECKED`** — the probe could not run, so nothing is proven. + Do not treat this as stale mergeability and do not treat it as Step 5's + real-conflict case; report the unchecked PR separately for manual retry + or setup repair. ## Step 6: Prove the final state diff --git a/product/skills/admin-bypass-sweep/tests/fires_step5a_names_probe.md b/product/skills/admin-bypass-sweep/tests/fires_step5a_names_probe.md new file mode 100644 index 00000000..f5767180 --- /dev/null +++ b/product/skills/admin-bypass-sweep/tests/fires_step5a_names_probe.md @@ -0,0 +1,4 @@ +Fixture assertion: the Step 5a passage in +`product/skills/admin-bypass-sweep/SKILL.md` must name +`scripts/probe_branch_rebase.sh`, must name all three probe outcomes +`OK`, `FAIL`, and `UNCHECKED`, and must not contain `rm -rf`. diff --git a/product/skills/admin-bypass-sweep/tests/test_step5a_probe_passage.py b/product/skills/admin-bypass-sweep/tests/test_step5a_probe_passage.py new file mode 100644 index 00000000..ba7aabef --- /dev/null +++ b/product/skills/admin-bypass-sweep/tests/test_step5a_probe_passage.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +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") +FIXTURE = (SKILL_DIR / "tests" / "fires_step5a_names_probe.md").read_text(encoding="utf-8") + + +def step5a_passage() -> str: + match = re.search(r"## Step 5a:.*?(?=\n## Step 6:)", SKILL, re.S) + if not match: + raise AssertionError("Step 5a passage not found") + return match.group(0) + + +class TestStep5aProbePassage(unittest.TestCase): + def test_fixture_names_the_assertions(self): + self.assertIn("scripts/probe_branch_rebase.sh", FIXTURE) + for outcome in ("OK", "FAIL", "UNCHECKED"): + self.assertIn(outcome, FIXTURE) + self.assertIn("rm -rf", FIXTURE) + + def test_step5a_names_probe_and_all_three_outcomes(self): + passage = step5a_passage() + self.assertIn("scripts/probe_branch_rebase.sh", passage) + for outcome in ("OK", "FAIL", "UNCHECKED"): + self.assertRegex(passage, rf"`{outcome}`") + + def test_step5a_no_longer_contains_improvised_removal(self): + self.assertNotIn("rm -rf", step5a_passage()) + + +if __name__ == "__main__": + unittest.main()