From 1baff71a975bbafb50c69aa3a8506ec58d39e330 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Sun, 23 Aug 2026 11:09:18 -0400 Subject: [PATCH] =?UTF-8?q?feat(skills):=20/prm=20=E2=80=94=20the=20"PR,?= =?UTF-8?q?=20CI=20green,=20then=20merge"=20wrap-up=20door?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a composition skill for the sentence typed at the end of nearly every task. `/prm` resolves the feature PR (argument / current branch / the claimed task's `library-pr:` + `workspace-pr:` in Mind's active.md), judges CI over EVERY workflow run for the head sha and EVERY matrix leg inside it, merges in library-first order, then hands back to the ship skills' completion steps. It owns no gate of its own: it refuses on red, pending, conflicting, or an unmerged upstream library PR, fetches a failing job's log before GitHub purges the blob, and asks before closing the issue — typing /prm authorizes the merge, not the close. Pure `gh` (2.4.0-safe: no `gh search`), so it runs on the CLI, mobile Claude Code chat and Codex alike; the committed .claude/.codex discovery links are what make it register in web/cloud sessions. - skills/prm/{SKILL.md,prm.md,reference.md} — 127-line mandatory load - skills/COMMANDS.md: registered as a tier-4 composition door - AGENTS.md: named in the command-surface prose - ship_library / ship_workspace: point their merge step at /prm - regenerated committed discovery (also picks up the missing `sizing` links) Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UrgmL8WMK82GxQJjPL8ZCs --- .claude/commands/prm.md | 1 + .claude/skills/prm | 1 + .claude/skills/sizing | 1 + .codex/skills/prm | 1 + .codex/skills/sizing | 1 + AGENTS.md | 3 +- skills/COMMANDS.md | 9 ++ skills/prm/SKILL.md | 14 +++ skills/prm/prm.md | 113 ++++++++++++++++++++++++ skills/prm/reference.md | 109 +++++++++++++++++++++++ skills/ship_library/ship_library.md | 3 + skills/ship_workspace/ship_workspace.md | 3 + 12 files changed, 258 insertions(+), 1 deletion(-) create mode 120000 .claude/commands/prm.md create mode 120000 .claude/skills/prm create mode 120000 .claude/skills/sizing create mode 120000 .codex/skills/prm create mode 120000 .codex/skills/sizing create mode 100644 skills/prm/SKILL.md create mode 100644 skills/prm/prm.md create mode 100644 skills/prm/reference.md diff --git a/.claude/commands/prm.md b/.claude/commands/prm.md new file mode 120000 index 0000000..deaa58e --- /dev/null +++ b/.claude/commands/prm.md @@ -0,0 +1 @@ +../../skills/prm/prm.md \ No newline at end of file diff --git a/.claude/skills/prm b/.claude/skills/prm new file mode 120000 index 0000000..ae66b0e --- /dev/null +++ b/.claude/skills/prm @@ -0,0 +1 @@ +../../skills/prm \ No newline at end of file diff --git a/.claude/skills/sizing b/.claude/skills/sizing new file mode 120000 index 0000000..81bbb54 --- /dev/null +++ b/.claude/skills/sizing @@ -0,0 +1 @@ +../../skills/sizing \ No newline at end of file diff --git a/.codex/skills/prm b/.codex/skills/prm new file mode 120000 index 0000000..ae66b0e --- /dev/null +++ b/.codex/skills/prm @@ -0,0 +1 @@ +../../skills/prm \ No newline at end of file diff --git a/.codex/skills/sizing b/.codex/skills/sizing new file mode 120000 index 0000000..81bbb54 --- /dev/null +++ b/.codex/skills/sizing @@ -0,0 +1 @@ +../../skills/sizing \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index b3802b4..edded74 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -169,7 +169,8 @@ via `/route`, and it routes to the right agent; normal usage never says "PyAutoBrain". A few commands are compositions rather than single agents: `/docs` and `/research` route through the dev-flow with their PyAutoMind work-type fixed (no dedicated conductor — added only on demonstrated need, never -for symmetry); `/wake_up` composes sync + `/health` + `/hygiene`; `/brain +for symmetry); `/wake_up` composes sync + `/health` + `/hygiene`; `/prm` composes the +end-of-task wrap-up (CI green → merge → `ship_*` completion); `/brain ` is the raw passthrough. Every command routes **through** the Brain; none replaces it. diff --git a/skills/COMMANDS.md b/skills/COMMANDS.md index 336977d..f1847d6 100644 --- a/skills/COMMANDS.md +++ b/skills/COMMANDS.md @@ -85,6 +85,15 @@ call, so the Brain is not bypassed): local-only steps. Interactive/terminal only (the automated morning webhooks are separate). +- **`/prm`** — the wrap-up shortcut: *"PR, CI green, then merge"*. Watches a + feature PR's checks until **every** workflow run and **every** matrix leg for + the head sha is green, merges in library-first order, then hands back to the + `ship_*` completion steps (Shipped comment, `lifecycle.py record`, cleanup). + Three keystrokes for the sentence the human types at the end of nearly every + task. Owns no gate of its own — it refuses on red/pending/conflicting and on an + unmerged upstream library PR, and asks before closing the issue. Pure `gh`, so + it runs on the CLI, mobile Claude Code chat and Codex alike. + **5. Maintenance doors** — periodic sweeps that reason about accumulated debris and then execute their own cleanup after per-bucket human confirmation. They own no agent, but unlike the composition doors they *mutate*, so each is diff --git a/skills/prm/SKILL.md b/skills/prm/SKILL.md new file mode 100644 index 0000000..c371e93 --- /dev/null +++ b/skills/prm/SKILL.md @@ -0,0 +1,14 @@ +--- +name: prm +description: The wrap-up shortcut — "PR, CI green, then merge". Watch a feature PR's checks until every workflow run and every matrix leg is green, then merge it (library-first gate honoured) and finish the ship — Mind record, issue comment, cleanup. Use when the user says merge this PR once CI is green, or types /prm. Runs anywhere gh is authenticated: CLI, mobile Claude Code chat, Codex. +--- + +# /prm — PR, CI green, then merge + +Follow [`prm.md`](prm.md) exactly; gh mechanics in [`reference.md`](reference.md). + +Composition door — it owns no agent and re-derives nothing: the merge gates come +from `/ship_library` / `/ship_workspace`, the CI verdict from GitHub Actions. +Typing `/prm` **is** the merge authorization; the skill still refuses to merge on +red, pending, conflicting, or an unmerged upstream library PR, and still asks +before closing the issue. diff --git a/skills/prm/prm.md b/skills/prm/prm.md new file mode 100644 index 0000000..2cd91be --- /dev/null +++ b/skills/prm/prm.md @@ -0,0 +1,113 @@ +# /prm — PR, CI green, then merge + +The end-of-task shortcut for "PR CI green then merge" — three keystrokes instead +of the sentence. It watches the feature PR's checks, merges the moment they are +genuinely green, and finishes the ship (Mind record, issue comment, cleanup). + +Shared routing context: `PyAutoBrain/skills/COMMANDS.md`. +gh mechanics + snippets: [`reference.md`](reference.md). + +## Principle: compose, don't recompute + +Every rule here already has an owner. The merge gates belong to +[`../ship_library/ship_library.md`](../ship_library/ship_library.md) and +[`../ship_workspace/ship_workspace.md`](../ship_workspace/ship_workspace.md) +(library-first gate, issue completion, Mind state); the verdict belongs to GitHub +Actions. `/prm` only sequences them. It never re-runs the readiness gate, never +edits code to make a check pass, and never opens a PR — that is `/ship_*`. + +## Usage + +``` +/prm # the PR for the current branch / current task +/prm 380 # PR #380 in the current or inferred repo +/prm PyAutoArray#42 # or Jammy2211/PyAutoArray#42, or the full PR URL +/prm --no-wait # judge CI once and report; merge only if already green +``` + +## Environment: runs anywhere gh is authenticated + +Local CLI, mobile Claude Code chat, and Codex all work — every step is `gh`, no +checkout required. Detect which you are in: + +- **Local** — `$PYAUTO_ROOT` (default `~/Code/PyAutoLabs`) holds the sibling + repos. Branch detection and post-merge worktree cleanup are available. +- **Remote (mobile/codex)** — no multi-repo checkout. Resolve the PR from the + argument or by listing candidates (below), and **skip the local-only cleanup** + with a one-line note. Never `cd` into a repo that isn't there. + +## The routine + +### 1. Resolve the target PR(s) + +In order: explicit argument → current branch (`gh pr view --json` in the repo you +are in) → the claimed task in `PyAutoMind/active.md` (its `library-pr:` / +`workspace-pr:` entries; on mobile read it with `gh api`) → `gh pr list` across +the claimed repos. If more than one candidate survives, **list them numbered and +ask once** — never guess which PR to merge. Report each target as +`owner/repo#N — title — branch` before doing anything. + +A task that shipped both a library and a workspace PR is **one** `/prm` run over +both, merged in gate order (step 4). + +### 2. Judge CI honestly — every run, every leg + +A head sha triggers **two** runs of each workflow (`push` and `pull_request`), +each with its own matrix legs. One green row is not "CI green". Enumerate every +run for the head sha and every job inside it (snippets in `reference.md`), and +treat any run that is not `completed` as *not ready* — not "green so far". +`mergeStateStatus=UNSTABLE` is the tell that something is still pending or red. + +Also read `mergeable` and `mergeStateStatus`: `CONFLICTING` / `BEHIND` / +`BLOCKED` stops the run with the reason, whatever the checks say. + +### 3. Wait, or stop + +- **Pending** — poll every ~90s, one compact line per poll (`3/4 legs done`). + Cap at ~30 min; then report where it stands and stop rather than spinning. + Under `--no-wait`, skip the loop: report and stop. +- **Red** — **fetch the failing job's log immediately** (GitHub purges the blob; + once purged you can never name what broke), quote the failing step, and stop. + Do not merge, do not re-run, do not "wait for the flake to pass". Offer the + next door: `/bug` for a real failure, or a named re-run if the user judges it a + known flake — their call, not yours. +- **No checks configured** — say so explicitly and ask before merging. + +### 4. Merge + +Green on every leg → merge, in this order: + +1. **Library PR first.** The workspace PR may not merge until its upstream + library PR is `MERGED` — the library-first gate + ([`../ship_workspace/reference.md`](../ship_workspace/reference.md)). Refuse + otherwise; there is no `--auto`-flag workaround. +2. `gh pr merge --merge` per PR (add `-R owner/repo` when you have no + checkout), then confirm the resulting state is `MERGED` — a queued or + auto-merge state is not a merge. Do **not** pass `--delete-branch`: it deletes + the local branch too, which fails or orphans a task worktree. Branch deletion + belongs to the post-merge cleanup in step 5. + +Never force, never override a protection, never rewrite history. If a merge is +refused by GitHub, report the reason verbatim and stop. + +### 5. Finish the ship + +Hand back to the ship skills' completion steps — do not re-invent them: + +- Post the "Shipped" comment on the issue (templates: `../ship_library/reference.md` + → "Issue comments + Mind state"). +- Write the dated completion record — `PyAutoMind/scripts/lifecycle.py record` + (also refreshes the index and prunes the `active.md` entry) — and push Mind. +- **Ask before closing the issue.** Merging is what `/prm` was typed for; closing + is a separate decision the human makes. +- **Local only:** offer the post-merge cleanup (worktree removal, local branch + deletion) per the ship skills; on mobile/codex note it as still pending. + +## Notes + +- `/prm` merges an **existing** PR. No PR yet → `/ship_library` or + `/ship_workspace` first; `/prm` will say so rather than opening one. +- It never bypasses the Heart readiness gate — that gate ran at ship time, and a + red Heart is not something a merge shortcut may re-judge. +- Under a `--auto` workflow run, merge stays human: `/prm` is a human-typed door + and is never invoked by the autonomous queue. diff --git a/skills/prm/reference.md b/skills/prm/reference.md new file mode 100644 index 0000000..0647d20 --- /dev/null +++ b/skills/prm/reference.md @@ -0,0 +1,109 @@ +# /prm — reference + +Mechanics for [`prm.md`](prm.md). Lazy-loaded: read it when you actually run the +step. `gh` in this workspace is old (2.4.0) — it has **no `gh search`** and some +newer flags are absent, so everything below sticks to `gh pr` / `gh api`. + +## Resolving the PR + +```bash +# From the current branch (local checkout) +gh pr view --json number,url,title,headRefName,state,labels + +# Explicit target, no checkout needed (mobile / Codex) +gh pr view -R / --json number,url,title,headRefName,state + +# Candidates when nothing is claimed: open PRs in one repo +gh pr list -R / --state open --json number,title,headRefName,labels + +# The claimed task's PRs, read straight from Mind without a checkout +gh api repos/PyAutoLabs/PyAutoMind/contents/active.md --jq '.content' \ + | base64 -d | grep -iE 'library-pr:|workspace-pr:|issue:| - ' +``` + +Feature PRs from `/ship_*` carry the `pending-release` label — a useful filter, +never a merge blocker (the label describes the release state of the change, not +the mergeability of the PR). + +## CI: every run, every leg + +A single head sha gets **two** runs of each workflow (`push` + `pull_request`, +created seconds apart), each with its own matrix legs (e.g. py3.12 + py3.13). So +one `success` row is 1 of 4 signals. Enumerate all of them: + +```bash +repo=/; pr= +sha=$(gh api repos/$repo/pulls/$pr --jq '.head.sha') + +# Every run for the sha +gh api "repos/$repo/actions/runs?head_sha=$sha" \ + --jq '.workflow_runs[] | "\(.name) [\(.event)]: \(.status)/\(.conclusion)"' + +# Every job within every run +for run in $(gh api "repos/$repo/actions/runs?head_sha=$sha" --jq '.workflow_runs[].id'); do + gh api repos/$repo/actions/runs/$run/jobs --jq '.jobs[] | " \(.name)=\(.conclusion)"' +done + +# Not-ready test: non-zero means keep waiting, NOT "green so far" +gh api "repos/$repo/actions/runs?head_sha=$sha" \ + --jq '[.workflow_runs[] | select(.status!="completed")] | length' +``` + +Cross-check the PR's own view — mergeability is independent of the checks: + +```bash +gh pr view $pr -R $repo --json state,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup +``` + +- `mergeStateStatus`: `CLEAN` → mergeable · `UNSTABLE` → a check is pending or + failed · `BLOCKED` → protection rule / review missing · `BEHIND` → base moved · + `DIRTY` → conflicts. Only `CLEAN` merges without a human decision. +- `mergeable`: `CONFLICTING` stops the run regardless of check colour. + +An empty run list is not green — it means no workflow fired for that sha (a +docs-only path filter, a skipped event, or Actions being down). Say which. + +## A red leg: grab the log *now* + +GitHub purges job logs; once purged, `BlobNotFound` leaves the failure +unnameable forever. Fetch before reporting: + +```bash +job=$(gh api repos/$repo/actions/runs/$run/jobs --jq '.jobs[] | select(.conclusion=="failure") | .id') +gh api repos/$repo/actions/jobs/$job/logs > /tmp/prm-failing-job.log +grep -nE 'FAILED|Error|assert|Traceback' /tmp/prm-failing-job.log | tail -40 +``` + +Report the failing job name, the failing step, and the quoted error. A failure +that matches a known flake is still a failure — the user decides whether to +re-run; `/prm` does not decide that for them. + +## Merging in gate order + +```bash +# 1. library PR +gh pr merge -R / --merge +gh pr view -R / --json state --jq '.state' # must print MERGED + +# 2. only then the workspace PR +gh pr view -R / --json body --jq '.body' | grep -iE 'PyAuto[A-Za-z]+/pull/[0-9]+' +gh pr merge -R / --merge +``` + +The library-first gate is `../ship_workspace/reference.md` → "Library-first merge +gate": a workspace PR linked to an upstream library PR may only merge once that +PR reads `MERGED`. There is no workaround — not `--auto`, not `--admin`. + +## Finishing + +Completion is the ship skills' contract, unchanged: + +- "Shipped" comment template → `../ship_library/reference.md` → "Issue comments + + Mind state". +- `python3 PyAutoMind/scripts/lifecycle.py record --prompt ` — + the argument is the **bare prompt filename**, not a path — then commit and push + Mind (on `main`, and check that first). +- Closing the issue is a separate human decision: ask, don't assume. +- Local post-merge cleanup (worktree removal, local + remote branch deletion) → + the `ship_library` / `ship_workspace` cleanup sections. On mobile/Codex, say + it is still pending rather than pretending it ran. diff --git a/skills/ship_library/ship_library.md b/skills/ship_library/ship_library.md index 2a7260b..84b5011 100644 --- a/skills/ship_library/ship_library.md +++ b/skills/ship_library/ship_library.md @@ -112,4 +112,7 @@ next human (or queued) launch. - This skill ships **library source only** — workspace scripts/notebooks go through `/ship_workspace`. +- Waiting on CI before the merge is [`/prm`](../prm/prm.md) — the wrap-up door + that judges every run/leg, merges in library-first order, and returns here for + the completion steps. - Never skip the readiness gate; never `--no-verify`; fix the underlying issue. diff --git a/skills/ship_workspace/ship_workspace.md b/skills/ship_workspace/ship_workspace.md index 8387a6d..3ac92e2 100644 --- a/skills/ship_workspace/ship_workspace.md +++ b/skills/ship_workspace/ship_workspace.md @@ -99,5 +99,8 @@ and prunes the `active.md` entry), and push Mind. Templates in ## Notes - Workspace scripts/notebooks/configs only — never library source. +- If CI is still running when step 5 arrives, [`/prm`](../prm/prm.md) is the door + that waits for every run/leg, merges behind the library-first gate, and comes + back to step 6. - Only edit `scripts/`; notebooks are regenerated. - Never skip the readiness gate or the library-first merge gate.